Skip to content

fix: stop wiki summary worker crash on long sessions#298

Merged
efenocchi merged 7 commits into
mainfrom
fix/codex-stop-summary-crash
Jul 1, 2026
Merged

fix: stop wiki summary worker crash on long sessions#298
efenocchi merged 7 commits into
mainfrom
fix/codex-stop-summary-crash

Conversation

@efenocchi

@efenocchi efenocchi commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Problem

The Stop hook's background wiki-summary worker crashed on long sessions (reported on a ~4480-event Codex session): ENOBUFS (the agent's stdout exceeded Node's 1 MB default execFileSync buffer) and ETIMEDOUT (120s) because the worker re-fed the entire session to the summarizer on every run.

Root cause: the offset that should bound each run to only the new rows was never applied — no rows.slice() existed, so the full session was always written to the JSONL; and the offset value was parsed by regex from the LLM-generated summary text (fragile) instead of the reliable sidecar counter. When the summary failed, finalizeSummary never ran, so the offset stayed at 0 and every run reprocessed everything.

Fix (all 5 workers: claude-code, codex, cursor, hermes, pi)

  • maxBuffer: 64 MB on the summarizer execFileSync → eliminates ENOBUFS (the summary is written to a file, not read from stdout).
  • Read the offset from the sidecar (readState().lastSummaryCount), with the regex on the stored summary kept only as a cross-machine fallback.
  • Actually slice rows.slice(prevOffset) → only new rows reach the agent; the run is skipped entirely when nothing is new.
  • New shared helper src/hooks/wiki-offset.ts:
    • stampOffset — the worker writes the offset into the summary itself, removing the dependency on the LLM echoing a bookkeeping line.
    • capLinesByBytes — 4 MB tail cap safety net (keeps the newest rows).
  • Added the execSucceeded guard to cursor/hermes/pi/claude-code (codex already had it) so a failed run on a resumed session never advances the offset past rows that were never summarized.

OpenClaw is N/A (pure HTTP/WebSocket gateway — does not run the wiki-worker daemon). MCP/Cowork reuses the base worker.

Known tradeoff

When a single increment exceeds 4 MB (degenerate case: first summary of an already-huge backlog with offset 0, or a lone giant row) the byte cap keeps the newest rows and permanently skips the oldest ones (logged, not silent). Intentional — documented in the capLinesByBytes doc comment. In normal incremental operation increments are tiny and nothing is ever dropped.

Tests

tsc clean; 77 worker/helper tests pass. New coverage: offset slicing feeds only new rows, byte-cap keeps the tail and reports drops, no-new-rows skip, maxBuffer presence, and the failure guard that prevents offset advance (data loss) on a failed resumed run.

Summary by CodeRabbit

  • New Features
    • Wiki summarization resumption now uses a persisted per-session progress counter as the authoritative resume point, processing only newly added events.
    • Added stricter per-run JSONL byte limiting with byte-safe truncation and dropped-line tracking.
  • Bug Fixes
    • Prevents offset advancement, uploads, and finalization when the summarizer run fails or produces unchanged/empty output.
    • Skips summarization when there are no new events to process.
    • Reduces subprocess failures by increasing stdout buffering capacity.
  • Tests
    • Expanded resume/offset, JSONL capping, buffering, and failure-mode coverage across workers, plus new unit tests for offset stamping and capping.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f5709179-9e4d-4a02-92d9-69d11edd4e78

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds shared wiki offset helpers and updates multiple wiki workers to resume from persisted sidecar counts, process only new session rows, cap JSONL input size, and avoid uploading summaries after failed executions. Related tests were extended for the new resume, skip, and failure paths.

Changes

Wiki worker offset resumption

Layer / File(s) Summary
Offset and byte-capping utilities
src/hooks/wiki-offset.ts, tests/shared/wiki-offset.test.ts
Adds shared offset parsing/stamping helpers, JSONL byte capping, and tests for overwrite, insertion, and truncation behavior.
Claude wiki-worker resume and upload flow
src/hooks/wiki-worker.ts, tests/claude-code/wiki-worker.test.ts, tests/claude-code/wiki-worker-plugin-version.test.ts
Uses sidecar state for resumption, caps JSONL input, tracks execution success, stamps offsets on upload, and skips upload on failure or when no new rows exist.
Codex wiki-worker resume and upload flow
src/hooks/codex/wiki-worker.ts, tests/codex/codex-wiki-worker.test.ts
Applies the same resume/cap/upload gating pattern for Codex, including larger maxBuffer handling and offset-stamping assertions.
Cursor wiki-worker resume and upload flow
src/hooks/cursor/wiki-worker.ts, tests/cursor/cursor-wiki-worker.test.ts
Applies sidecar-based resumption and capped input generation for Cursor, with failure-path upload skipping.
Hermes wiki-worker resume and upload flow
src/hooks/hermes/wiki-worker.ts, tests/hermes/hermes-wiki-worker.test.ts
Applies sidecar-based resumption and capped input generation for Hermes, with failure-path upload skipping.
Pi wiki-worker resume and upload flow
src/hooks/pi/wiki-worker.ts, tests/pi/pi-wiki-worker.test.ts
Applies sidecar-based resumption and capped input generation for Pi, with failure-path upload skipping.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • activeloopai/hivemind#218: Shares the Codex worker upload/failure path changes around skipping uploads when execution fails.

Suggested reviewers: kaghni

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately describes the main fix: preventing wiki summary worker crashes on long sessions.
Description check ✅ Passed It includes the problem, fix, tradeoff, and tests, but doesn't use the repo's exact Summary/Version Bump/Test plan template.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/codex-stop-summary-crash

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.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Scope: files changed in this PR. Enforced threshold: 90% per metric (per file via vitest.config.ts).

Status Category Percentage Covered / Total
🟢 Lines 90.96% (🎯 90%) 523 / 575
🔴 Statements 89.54% (🎯 90%) 565 / 631
🟢 Functions 95.00% (🎯 90%) 57 / 60
🔴 Branches 84.97% (🎯 90%) 311 / 366
File Coverage — 6 files changed
File Stmts Branches Functions Lines
src/hooks/codex/wiki-worker.ts 🟢 94.7% 🔴 84.1% 🟢 100.0% 🟢 96.6%
src/hooks/cursor/wiki-worker.ts 🔴 85.0% 🔴 82.5% 🟢 90.0% 🔴 86.7%
src/hooks/hermes/wiki-worker.ts 🔴 84.8% 🔴 82.5% 🟢 90.0% 🔴 86.5%
src/hooks/pi/wiki-worker.ts 🔴 84.8% 🔴 82.5% 🟢 90.0% 🔴 86.5%
src/hooks/wiki-offset.ts 🟢 97.0% 🔴 87.5% 🟢 100.0% 🟢 100.0%
src/hooks/wiki-worker.ts 🟢 94.6% 🟢 91.8% 🟢 100.0% 🟢 94.9%

Generated for commit 3cbeb7c.

@coderabbitai coderabbitai 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.

Actionable comments posted: 6

🧹 Nitpick comments (3)
src/hooks/wiki-offset.ts (1)

21-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate regex literals risk drift.

OFFSET_RE and the regex inside parseOffset encode the same pattern separately. If one changes without the other, stampOffset's detection and parseOffset's extraction desync, breaking the round-trip contract the tests assert ("Verifies that stamping an existing “JSONL offset” line changes the offset value and that parseOffset(out) returns the same number (round-trip contract).").

♻️ Proposed consolidation
-const OFFSET_RE = /\*\*JSONL offset\*\*:\s*\d+/;
+const OFFSET_RE = /\*\*JSONL offset\*\*:\s*(\d+)/;

 export function parseOffset(summary: string): number | null {
-  const m = summary.match(/\*\*JSONL offset\*\*:\s*(\d+)/);
+  const m = summary.match(OFFSET_RE);
   return m ? parseInt(m[1], 10) : null;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/wiki-offset.ts` around lines 21 - 28, The offset-matching logic in
OFFSET_RE and parseOffset is duplicated and can drift, breaking the
stampOffset/parseOffset round-trip. Consolidate the pattern in
src/hooks/wiki-offset.ts so parseOffset reuses OFFSET_RE (or a single shared
capture regex) instead of hardcoding its own regex, and keep the existing
symbols parseOffset and OFFSET_RE aligned with the worker read/write contract.
tests/codex/codex-wiki-worker.test.ts (1)

248-249: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make this log assertion specific to the expected offset/count.

toContain("no new events since last summary") is broader than the repo test rule and would still pass if the numeric details regressed. Assert the exact message for the lastSummaryCount = 3 / eventCount = 3 case instead. As per path instructions, tests/**: "Prefer asserting on specific values (paths, messages) over generic substrings."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/codex/codex-wiki-worker.test.ts` around lines 248 - 249, The wiki
worker test assertion is too broad and can miss regressions in the numeric
details. Update the `codex-wiki-worker.test.ts` check around
`readFileSync`/`expect(log)` to assert the exact log message for the
`lastSummaryCount = 3` and `eventCount = 3` case, rather than using a generic
substring match. Use the existing `wiki.log` assertion in this test to locate
the spot and make the expected offset/count explicit.

Source: Path instructions

tests/claude-code/wiki-worker.test.ts (1)

352-353: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the full log messages here.

Both checks only match generic substrings, so they won't catch regressions in the offset/total values or the skip-upload suffix. Assert the exact emitted messages for these scenarios instead. As per path instructions, tests/**: "Prefer asserting on specific values (paths, messages) over generic substrings."

Also applies to: 367-368

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/claude-code/wiki-worker.test.ts` around lines 352 - 353, The
wiki-worker log assertions are too generic and can miss regressions in the
emitted offset/total values and skip-upload suffix. Update the checks around the
wiki.log reads in the relevant test cases to assert the full expected log
messages exactly instead of using substring matches, so the scenarios covered by
the wiki worker logging stay precise.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hooks/codex/wiki-worker.ts`:
- Around line 236-245: The offset upload logic in wiki-worker’s summary handling
should not advance on a failed codex exec merely because tmpSummary changed. In
the block around raw/summaryChanged and the stampOffset path, gate finalizing
jsonlLines on execSucceeded, and for failed runs only upload when there is a
confirmed valid summary state rather than a partial write. Keep the existing
readFileSync/tmpSummary flow, but make the condition in the summaryChanged check
and the subsequent stampOffset handling in wiki-worker.ts prevent persisting a
new offset after timeout/non-zero exit.

In `@src/hooks/cursor/wiki-worker.ts`:
- Around line 223-231: The offset handling in cursor-worker should not finalize
or advance after a failed cursor-agent exec just because tmpSummary changed.
Update the logic around summaryChanged, execSucceeded, and stampOffset so only
successful executions can stamp and persist a new offset, while failed runs with
partial raw output are ignored or retried without slicing jsonlLines. Use the
cursor-agent --print flow in wiki-worker to locate the guard and keep the
persisted summary authoritative only on success.

In `@src/hooks/hermes/wiki-worker.ts`:
- Around line 230-238: Do not finalize or advance the offset when
`execSucceeded` is false, even if `summaryChanged` is true, because a failed
hermes run may have only produced a partial `tmpSummary`. Update the flow in
`wiki-worker.ts` around `summaryChanged`, `stampOffset`, and the upload path so
offset stamping and persistence only happen after a successful exec, and failed
runs simply skip upload without consuming `jsonlLines`.

In `@src/hooks/pi/wiki-worker.ts`:
- Around line 229-237: The offset update in wiki-worker’s summary handling is
too permissive: a failed pi --print run can still set summaryChanged and advance
lastSummaryCount even when the output is only a partial tmpSummary. In the logic
around execSucceeded, summaryChanged, and the stampOffset/raw upload path, gate
any offset advancement on execSucceeded being true (or otherwise confirm a valid
new summary) so failed runs never persist a higher lastSummaryCount. Keep the
skip-upload behavior for failed executions without a real summary change, and
ensure the code path that writes the stamped summary does not advance the offset
after failure.

In `@src/hooks/wiki-worker.ts`:
- Around line 210-216: The tail-cap logic in `capLinesByBytes` still allows one
oversized newest row to slip through, so `tmpJsonl` can exceed
`WIKI_JSONL_MAX_BYTES`. Update the handling in `wiki-worker.ts` around
`newRows`, `capLinesByBytes`, and `writeFileSync` so any single line larger than
the limit is rejected or truncated before being written, rather than being
unconditionally accepted as the first kept entry. Keep the existing
summary/logging flow, but ensure the final JSONL buffer always stays within the
byte cap.
- Around line 198-205: The offset handling in wiki-worker should not trust
readState(cfg.sessionId)?.lastSummaryCount unless the prior summary was actually
loaded from SELECT summary. In the logic around prevOffset and newRows, add a
guard so the stored lastSummaryCount is only applied when the summary seed was
successfully retrieved; otherwise fall back to 0 and process from the start. Use
the existing readState and prevOffset/newRows flow in src/hooks/wiki-worker.ts
to locate the change.

---

Nitpick comments:
In `@src/hooks/wiki-offset.ts`:
- Around line 21-28: The offset-matching logic in OFFSET_RE and parseOffset is
duplicated and can drift, breaking the stampOffset/parseOffset round-trip.
Consolidate the pattern in src/hooks/wiki-offset.ts so parseOffset reuses
OFFSET_RE (or a single shared capture regex) instead of hardcoding its own
regex, and keep the existing symbols parseOffset and OFFSET_RE aligned with the
worker read/write contract.

In `@tests/claude-code/wiki-worker.test.ts`:
- Around line 352-353: The wiki-worker log assertions are too generic and can
miss regressions in the emitted offset/total values and skip-upload suffix.
Update the checks around the wiki.log reads in the relevant test cases to assert
the full expected log messages exactly instead of using substring matches, so
the scenarios covered by the wiki worker logging stay precise.

In `@tests/codex/codex-wiki-worker.test.ts`:
- Around line 248-249: The wiki worker test assertion is too broad and can miss
regressions in the numeric details. Update the `codex-wiki-worker.test.ts` check
around `readFileSync`/`expect(log)` to assert the exact log message for the
`lastSummaryCount = 3` and `eventCount = 3` case, rather than using a generic
substring match. Use the existing `wiki.log` assertion in this test to locate
the spot and make the expected offset/count explicit.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 54992d03-de5d-4b5d-95bc-9b8a1714dbae

📥 Commits

Reviewing files that changed from the base of the PR and between 216c85e and 3b116eb.

📒 Files selected for processing (13)
  • src/hooks/codex/wiki-worker.ts
  • src/hooks/cursor/wiki-worker.ts
  • src/hooks/hermes/wiki-worker.ts
  • src/hooks/pi/wiki-worker.ts
  • src/hooks/wiki-offset.ts
  • src/hooks/wiki-worker.ts
  • tests/claude-code/wiki-worker-plugin-version.test.ts
  • tests/claude-code/wiki-worker.test.ts
  • tests/codex/codex-wiki-worker.test.ts
  • tests/cursor/cursor-wiki-worker.test.ts
  • tests/hermes/hermes-wiki-worker.test.ts
  • tests/pi/pi-wiki-worker.test.ts
  • tests/shared/wiki-offset.test.ts

Comment thread src/hooks/codex/wiki-worker.ts
Comment thread src/hooks/cursor/wiki-worker.ts
Comment thread src/hooks/hermes/wiki-worker.ts
Comment thread src/hooks/pi/wiki-worker.ts
Comment thread src/hooks/wiki-worker.ts Outdated
Comment thread src/hooks/wiki-worker.ts

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hooks/hermes/wiki-worker.ts (1)

147-170: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Don’t treat summary lookup failures as “no existing summary.”

Line 159 swallows any query failure and then continues with prevOffset = 0. If the canonical summary exists but the lookup temporarily fails, the worker can proceed, cap to the newest rows, and upload a replacement summary without the previous base.

Proposed safe failure handling
-    } catch { /* no existing summary */ }
+    } catch (e: any) {
+      wlog(`existing summary lookup failed: ${e.message}; skipping to avoid overwriting an unknown base summary`);
+      return;
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/hermes/wiki-worker.ts` around lines 147 - 170, The summary lookup
in wiki-worker is treating any query failure as if no existing summary were
found, which can incorrectly reset the offset and overwrite a valid canonical
summary. Update the try/catch around the summary fetch in the logic that reads
from query(), tmpSummary, and hasExistingSummary so only a confirmed empty
result sets hasExistingSummary false; if query fails, preserve the existing
offset state and avoid falling back to prevOffset = 0. Keep the existing summary
path handling in the same section so stale-sidecar logic still applies only when
the summary was actually loaded.
🧹 Nitpick comments (1)
tests/codex/codex-wiki-worker.test.ts (1)

265-265: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the full failure log message.

This only checks a fragment, so a weaker or different failure message could still pass. Prefer the exact expected message text here.

Proposed test tightening
-    expect(log).toContain("failed after a partial summary write");
+    expect(log).toContain("codex exec failed after a partial summary write; skipping upload to avoid advancing the offset");

As per path instructions, tests/**: Prefer asserting on specific values (paths, messages) over generic substrings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/codex/codex-wiki-worker.test.ts` at line 265, The test in
codex-wiki-worker should assert the complete failure log message instead of a
substring, since checking only “failed after a partial summary write” allows
weaker messages to pass. Update the expectation in the relevant
codex-wiki-worker test to match the exact log text produced by the failure path,
using the existing log assertion around log so the test verifies the full
message precisely.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/hooks/hermes/wiki-worker.ts`:
- Around line 147-170: The summary lookup in wiki-worker is treating any query
failure as if no existing summary were found, which can incorrectly reset the
offset and overwrite a valid canonical summary. Update the try/catch around the
summary fetch in the logic that reads from query(), tmpSummary, and
hasExistingSummary so only a confirmed empty result sets hasExistingSummary
false; if query fails, preserve the existing offset state and avoid falling back
to prevOffset = 0. Keep the existing summary path handling in the same section
so stale-sidecar logic still applies only when the summary was actually loaded.

---

Nitpick comments:
In `@tests/codex/codex-wiki-worker.test.ts`:
- Line 265: The test in codex-wiki-worker should assert the complete failure log
message instead of a substring, since checking only “failed after a partial
summary write” allows weaker messages to pass. Update the expectation in the
relevant codex-wiki-worker test to match the exact log text produced by the
failure path, using the existing log assertion around log so the test verifies
the full message precisely.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 55c4f0e1-680b-4f60-8d88-adc678bacf5d

📥 Commits

Reviewing files that changed from the base of the PR and between 3b116eb and c166378.

📒 Files selected for processing (9)
  • src/hooks/codex/wiki-worker.ts
  • src/hooks/cursor/wiki-worker.ts
  • src/hooks/hermes/wiki-worker.ts
  • src/hooks/pi/wiki-worker.ts
  • src/hooks/wiki-offset.ts
  • src/hooks/wiki-worker.ts
  • tests/claude-code/wiki-worker.test.ts
  • tests/codex/codex-wiki-worker.test.ts
  • tests/shared/wiki-offset.test.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • tests/shared/wiki-offset.test.ts
  • src/hooks/pi/wiki-worker.ts
  • tests/claude-code/wiki-worker.test.ts
  • src/hooks/cursor/wiki-worker.ts
  • src/hooks/wiki-worker.ts
  • src/hooks/codex/wiki-worker.ts

@efenocchi

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@efenocchi efenocchi merged commit c82dcce into main Jul 1, 2026
11 checks passed
@efenocchi efenocchi deleted the fix/codex-stop-summary-crash branch July 1, 2026 17:05
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.

2 participants