Skip to content

fix: bound stuck generation recovery#563

Merged
bhekanik merged 4 commits into
mainfrom
codex/bound-stuck-generation-recovery
Jul 8, 2026
Merged

fix: bound stuck generation recovery#563
bhekanik merged 4 commits into
mainfrom
codex/bound-stuck-generation-recovery

Conversation

@bhekanik

@bhekanik bhekanik commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Bound stuck generation and message recovery batches so one maintenance run cannot sweep the whole table.
  • Keep staleness predicates on the update path and skip requests with fresh heartbeating sessions.
  • Add recovery-query indexes and remove stale bd workflow instructions from AGENTS.md.

Validation

  • bun install --frozen-lockfile
  • bun run build
  • bun --filter=@blah-chat/jobs run test:run src/trigger/recover-stuck-generations.test.ts src/trigger/recover-stuck-messages.test.ts
  • bun --filter=@blah-chat/jobs run typecheck
  • bun --filter=@blah-chat/persistence-postgres run typecheck
  • bun run lint
  • bun run typecheck
  • bun run test:run

Risk

  • Includes a Postgres migration adding indexes on generation_requests, generation_sessions, and messages for the maintenance queries.

Summary by cubic

Bound stuck generation/message recovery to capped batches and re-queue recovered requests instead of processing inline. Added staleness-safe updates, oldest-first selection with new indexes, idempotent enqueue (concurrency key + TTL), and rollback of resets if enqueue fails.

  • Dependencies

    • Patched tooling: vite to 7.3.5, undici to 7.28.0 (and @expo/cli’s undici to 6.27.0); bumped internal packages.
  • Migration

    • Adds indexes: generation_requests_by_status_updated, generation_sessions_by_request_updated, generation_sessions_by_message_status_updated, messages_by_status_updated.
    • Run the DB migration in @blah-chat/persistence-postgres.

Written for commit 18a28ca. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Recovery jobs now run in bounded batches, helping large backlogs clear more smoothly.
    • Stuck messages and requests are recovered more safely, with better handling of partial progress.
  • Bug Fixes

    • Improved recovery behavior to avoid reprocessing items inline and to respect batch limits.
    • Added safeguards so only eligible stale items are updated.
  • Performance

    • Added database indexing to speed up common status and timestamp lookups.
  • Documentation

    • Simplified session completion guidance with a shorter git-based checklist.

@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
blah-chat Ignored Ignored Jul 8, 2026 11:13pm

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Reworks recoverStuckGenerations and recoverStuckMessages to select-then-update stale rows in configurable, clamped batches, changes generation enqueue to use processGenerationTask.trigger with idempotency/concurrency options, adds supporting database indexes/migration, and simplifies the AGENTS.md session completion checklist.

Changes

Stuck Recovery Batching

Layer / File(s) Summary
recoverStuckGenerations batching and enqueue rework
packages/jobs/src/trigger/recover-stuck-generations.ts, packages/jobs/src/trigger/recover-stuck-generations.test.ts
Adds clamped batchSize dependency, replaces the single stale-update query with a select-then-update flow, and switches default enqueue to processGenerationTask.trigger with concurrencyKey/idempotencyKey/TTL; new tests verify trigger payload and batch limiting.
recoverStuckMessages batching rework
packages/jobs/src/trigger/recover-stuck-messages.ts, packages/jobs/src/trigger/recover-stuck-messages.test.ts
Adds clamped batchSize dependency with new default/max constants, replaces single update-with-returning with select-then-update of limited stale IDs, keeps staleness guard, and fails related generationSessions rows; new test verifies batch-limited recovery.
Supporting database indexes and migration
packages/persistence-postgres/src/schema.ts, packages/persistence-postgres/drizzle/0021_confused_doctor_strange.sql, packages/persistence-postgres/drizzle/meta/_journal.json
Adds composite btree indexes on messages, generationRequests, and generationSessions tables and the corresponding migration SQL and journal entry.

AGENTS.md Session Completion Update

Layer / File(s) Summary
Session completion checklist update
AGENTS.md
Replaces issue-tracking and bd workflow steps with a shorter checklist covering quality gates, git pull --rebase && git push, and git status verification.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Job as recoverStuckGenerations
  participant DB as generationRequests table
  participant Task as processGenerationTask

  Job->>DB: select stale request IDs (status, cutoff, limit batchSize)
  alt no stale IDs found
    Job-->>Job: return { recovered: 0 }
  else stale IDs found
    Job->>DB: update selected IDs to pending/updatedAt
    loop for each recovered request
      Job->>Task: trigger(requestId, concurrencyKey, idempotencyKey, idempotencyKeyTTL)
    end
  end
Loading

Possibly related PRs

  • planetaryescape/blah.chat#498: Modifies the same recoverStuckGenerations/recoverStuckMessages modules, overlapping directly with this PR's batching and enqueue changes.

Suggested reviewers: darul75

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: bounding stuck generation recovery.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/bound-stuck-generation-recovery

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR limits each stuck-generation maintenance pass. The main changes are:

  • Adds bounded select-then-update recovery for generation requests.
  • Dispatches recovered generation requests through process-generation tasks.
  • Adds bounded select-then-update recovery for stuck messages.
  • Adds indexes for the new recovery query shapes.
  • Updates maintenance tests and removes stale workflow notes.

Confidence Score: 4/5

The generation recovery path can leave reset requests without a dispatched processing task.

  • The select-then-update guards avoid clobbering fresh rows.
  • The migration indexes match the changed query shapes.
  • The changed generation dispatch boundary can strand rows when task dispatch fails after the status reset.
  • Large stuck-generation backlogs now recover slowly under the hourly schedule.

packages/jobs/src/trigger/recover-stuck-generations.ts

Important Files Changed

Filename Overview
packages/jobs/src/trigger/recover-stuck-generations.ts Adds bounded generation recovery and changes the default recovery action from inline processing to task dispatch.
packages/jobs/src/trigger/recover-stuck-messages.ts Adds bounded message recovery while keeping stale predicates on the update path.
packages/persistence-postgres/src/schema.ts Adds Drizzle index declarations for the recovery queries.
packages/persistence-postgres/drizzle/0021_confused_doctor_strange.sql Adds the matching Postgres indexes for generation requests, generation sessions, and messages.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Hourly maintenance] --> B[Select stale generation requests]
  B --> C[Reset selected rows to pending]
  C --> D[Trigger process-generation task]
  D --> E[Generation processing]
  D -. dispatch failure .-> F[Pending row without recovery retry]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
  A[Hourly maintenance] --> B[Select stale generation requests]
  B --> C[Reset selected rows to pending]
  C --> D[Trigger process-generation task]
  D --> E[Generation processing]
  D -. dispatch failure .-> F[Pending row without recovery retry]
Loading

Comments Outside Diff (1)

  1. packages/jobs/src/trigger/recover-stuck-messages.ts, line 77 (link)

    P2 Streaming Sessions Stay Stuck

    This session cleanup only marks pending sessions as error. If a stuck message belongs to a stale session that had already moved to another active state such as streaming, the message is recovered to error but the session keeps its active status, leaving stale generation session state behind.

Reviews (1): Last reviewed commit: "docs: remove bd workflow" | Re-trigger Greptile

Comment thread packages/jobs/src/trigger/recover-stuck-generations.ts
Comment thread packages/jobs/src/trigger/recover-stuck-generations.ts Outdated
Comment thread packages/jobs/src/trigger/recover-stuck-generations.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 9 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/persistence-postgres/drizzle/0021_confused_doctor_strange.sql">

<violation number="1" location="packages/persistence-postgres/drizzle/0021_confused_doctor_strange.sql:1">
P1: This migration builds new indexes with plain `CREATE INDEX`, which can block inserts/updates/deletes on these hot tables while each index is being built. On production-sized `messages` / generation tables, that can turn a deploy into a visible write outage. Using an online index build strategy (for example `CREATE INDEX CONCURRENTLY` run outside a transaction, or a staged migration process) would avoid that lock impact.</violation>
</file>

<file name="packages/jobs/src/trigger/recover-stuck-generations.ts">

<violation number="1" location="packages/jobs/src/trigger/recover-stuck-generations.ts:17">
P2: With `DEFAULT_RECOVERY_BATCH_SIZE = 25` and an hourly maintenance schedule, a worker outage leaving hundreds of requests stuck in `running`/`cancelling` would take many hours to fully recover (25 per hour). Consider whether 25 is sufficient for the expected failure scenarios, or whether a larger default (or adaptive batch sizing based on backlog depth) would better serve recovery SLAs.</violation>

<violation number="2" location="packages/jobs/src/trigger/recover-stuck-generations.ts:40">
P1: If `processGenerationTask.trigger()` throws mid-loop (e.g., rate limiting or network error), the batch UPDATE has already committed all rows to `pending`, but the remaining rows in the loop never get dispatched. Since the recovery query only selects `running` and `cancelling` statuses, those orphaned `pending` rows won't be picked up by future recovery runs—they'll be stuck indefinitely unless something else independently processes pending requests.

Consider either: (a) wrapping individual `trigger()` calls in try/catch so a single failure doesn't block the rest of the batch, or (b) dispatching before resetting status (trigger with idempotency, then update), or (c) using `batchTrigger()` to atomically enqueue all rows.</violation>
</file>

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

Comment thread packages/persistence-postgres/drizzle/0021_confused_doctor_strange.sql Outdated
Comment thread packages/jobs/src/trigger/recover-stuck-generations.ts
Comment thread packages/jobs/src/trigger/recover-stuck-generations.ts Outdated
@codacy-production

Copy link
Copy Markdown
Contributor

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 5 duplication

Metric Results
Duplication 5

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@bhekanik bhekanik merged commit 6253c0d into main Jul 8, 2026
25 checks passed
@bhekanik bhekanik deleted the codex/bound-stuck-generation-recovery branch July 8, 2026 23:29
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