Skip to content

fix: batch-2 bug sweep — scheduler DST wedge, stale 429 masking, export route alias, rune-safe redaction, cutoff parity - #171

Merged
jkyberneees merged 5 commits into
mainfrom
fix/bug-hunt-b2
Sep 1, 2026
Merged

fix: batch-2 bug sweep — scheduler DST wedge, stale 429 masking, export route alias, rune-safe redaction, cutoff parity#171
jkyberneees merged 5 commits into
mainfrom
fix/bug-hunt-b2

Conversation

@jkyberneees

Copy link
Copy Markdown
Contributor

Batch 2 of the bug-hunt sweep (5 bugs, every fix RED-first).

Bugs fixed

  1. 🔴 Schedule.Next() infinite loop across DST fall-back (internal/schedule) — the hour-jump advanced via time.Date(...).Add(time.Hour). time.Date resolves an ambiguous wall time to its FIRST occurrence, so across a fall-back transition the hop could land back on the repeated wall hour and stop advancing entirely when that hour ∉ the hour mask — an infinite loop wedging the scheduler daemon and every schedule add/list/next. Falls back to duration arithmetic when the jump makes no progress. Reproduced: the RED test hung 5s pre-fix.

  2. Stale 429 state masked the real final failure (internal/llm) — lastStatus/lastBody were set on non-200 and never cleared: a 429 early in the retry loop wrapped a later malformed-200/streaming failure in RateLimitError — the exact type the serve turn handler reads as "provider throttled" (dead-prompt handling). Both buffered and streaming paths now reset the window on a 200.

  3. /api/sessions/{id}/export route alias (serve.go) — the suffix was stripped for ALL methods while only GET dispatches to export: DELETE …/export deleted the session and POST …/export renamed it — destructive aliases through a read-only route. Mirrors the GET-only /plan guard.

  4. redactGoal byte-truncation split UTF-8 runes (wire-v2 registry) — the constant promises chars; a multi-byte rune at the boundary was corrupted with invalid UTF-8 exactly when the clamp engaged. Now rune-safe.

  5. Dry-run cutoff math diverged from the sweep — sweep uses duration arithmetic (DST-safe), the preview used AddDate; up to an hour of files could be deleted that the preview never listed. maintenance.DaysAgo is now exported and shared.

Verification

  • 5 RED-first regression tests, each observed failing pre-fix (1 hang → watchdog, 1 capability-absent compile RED)
  • Full suites green: cmd/odek, internal/llm, internal/schedule, internal/maintenance
  • go vet clean; golangci-lint 0 issues; -race clean

The hour-jump advanced via time.Date(...).Add(time.Hour). time.Date
resolves an ambiguous wall time to its FIRST occurrence, so across a
DST fall-back transition the hop could land back on the repeated wall
hour and stop advancing entirely when that hour is not in the hour
mask — an infinite loop that wedged the scheduler daemon and every
schedule add/list/next invocation. When the jump makes no progress,
fall back to plain duration arithmetic, which crosses the transition
by construction.

RED-first regression test: TestNext_DstFallBackRepeatedHourNotInMask
(observed hanging 5s before the fix; hermetic via time/tzdata).
lastStatus/lastBody were set on non-200 responses and never cleared,
so a 429 early in the retry loop wrapped a LATER different failure in
RateLimitError on exhaustion — the exact type the serve turn handler
reads as 'provider throttled' (dead-prompt handling). A final
malformed-200 (buffered) or streaming failure after an earlier 429 now
reports its real cause. Fixed on both the buffered and streaming paths.

RED-first regression test: TestClient_Call_Stale429DoesNotMaskMalformed200.
redactGoal sliced by bytes while the constant promises chars: a
multi-byte rune at the boundary was split, corrupting the goal text
with invalid UTF-8 exactly when the clamp engaged (long goals are the
normal case for real tasks). Now truncates on a rune boundary.

RED-first regression test: TestRedactGoal_TruncationIsRuneSafe.
handleSessionByID stripped the /export suffix for ALL methods while
only GET dispatches to the export handler — so DELETE
/api/sessions/{id}/export fell through to the base-session delete
(destroying the session through a documented read-only route) and
POST .../export renamed it. Mirrors the GET-only /plan guard, which
exists for exactly this reason.

RED-first regression test: TestSessionExportSuffix_NotAliasedForMutatingMethods.
The sweep computes day-based retention cutoffs with duration
arithmetic (N*24h) to avoid DST-sensitive calendar math; the dry-run
preview used time.AddDate, so after a DST transition the previewed
deletion set diverged from the sweep's by up to an hour of files.
The helper is now exported (maintenance.DaysAgo) and shared, making
preview/sweep divergence impossible by construction.

RED observation: the regression test referenced the not-yet-existing
maintenance.DaysAgo (capability-absent compile RED), then passed.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 1, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
odek 7b03a1c Commit Preview URL

Branch Preview URL
Sep 01 2026, 07:19 AM

@jkyberneees
jkyberneees merged commit 073eead into main Sep 1, 2026
10 checks passed
@jkyberneees
jkyberneees deleted the fix/bug-hunt-b2 branch September 1, 2026 07:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant