Skip to content

fix(claude): detach the session-end akm index reindex (#90) - #92

Merged
itlackey merged 1 commit into
mainfrom
fix/session-end-detached-reindex
Aug 21, 2026
Merged

fix(claude): detach the session-end akm index reindex (#90)#92
itlackey merged 1 commit into
mainfrom
fix/session-end-detached-reindex

Conversation

@itlackey

Copy link
Copy Markdown
Owner

Fixes #90

Cause

sessionEnd() called runIndexOnSessionEnd(), which ran akm index through a blocking akmRunChecked(["index"], undefined, INDEX_TIMEOUT * 1000). That holds the hook process open for a full reindex. In headless mode (claude -p) the harness tears the hook down first and kills it, printing SessionEnd hook [...] failed: Hook cancelled, and:

  • the reindex is abandoned mid-run — exactly the half-built-index hazard the old comment already worried about, arriving via teardown instead of SIGTERM;
  • the process is killed outright, so runIndexOnSessionEnd() never reaches its own appendLog(SESSION_LOG, "akm_index_failed", ...) line and the failure is recorded nowhere in the plugin state dir.

Fix

Spawn the reindex detached: true + child.unref(), matching the two patterns already in the same file: sessionStart()'s reindex and extractSession(), which demonstrably survives the same teardown.

Following extractSession()'s precedent (see the EXTRACT_LOG comment near the top of the file), the child does not vanish into stdio: "ignore": its stdout/stderr are appended to STATE_DIR/index.log (owner-only, rotated like every other state file), and the spawn attempt itself is recorded in session.log as index_spawned. A spawn that throws still logs akm_index_failed. So the diagnosability that issue #90 reports as lost is preserved — a reindex that fails after the parent is gone leaves a trace in index.log.

runIndexOnSessionEnd() has exactly one caller (sessionEnd()), confirmed by grep, so no other path needed the checked/blocking behavior. AKM_INDEX_TIMEOUT no longer has a consumer and is removed along with its README row; index.log is documented in the state-file table.

No new config knobs, no console/stdout/stderr writes, subprocess boundary wrapped in try/catch with plugin-local logging (AGENTS.md).

Tests

Reused the existing harness in tests/claude-plugin.test.ts (runHook, waitFor, shellQuote, permissionBits):

  • New regression test session-end does not block on the reindex (issue #90): a fake akm that sleep 3s on index then touches a marker. Asserts the hook returns in well under 3s, that the marker does not yet exist when it returns (i.e. it did not block on a checked reindex), and that the detached child completes afterwards.
  • session-end records the spawn and captures the child's output in index.log (was session-end logs akm index failures without throwing): asserts index_spawned in session.log, the akm_index header in index.log, the failing child's stderr landing in index.log, and 0600 permissions.
  • session-end runs akm index and no longer writes a session_checkpoint memory now polls with waitFor since the fake CLI writes after the hook returns.

Verification

cd claude && bun install && bun run typecheck:

+ @types/bun@1.3.14
+ @types/node@26.1.1
+ typescript@5.9.3

5 packages installed [16.00ms]
$ tsc -p tsconfig.json

(clean, no diagnostics)

bun test tests/claude-plugin.test.ts:

bun test v1.3.14 (0d9b296a)

 81 pass
 0 fail
 308 expect() calls
Ran 81 tests across 1 file. [8.60s]

Full bun test from the repo root:

 193 pass
 4 skip
 2 fail
 1 error
 607 expect() calls
Ran 199 tests across 10 files. [8.90s]

The 2 failures + 1 error are pre-existing and unrelated — they are in tests/opencode-plugin.test.ts (Cannot find module '@opencode-ai/plugin') and tests/opencode-eval-harness.test.ts, both from OpenCode deps not being installed in this environment. Baseline on main with the change stashed: 192 pass, 4 skip, 2 fail, 1 error — same failures, one fewer passing test (the new regression test).

🤖 Generated with Claude Code

https://claude.ai/code/session_018aDbzuoe77LGKFre2Dtj2k

sessionEnd() ran `akm index` through a blocking akmRunChecked() with an
INDEX_TIMEOUT cap. In headless mode (`claude -p`) the harness tears the
hook process down as soon as the hook returns, so the reindex was killed
mid-run and the process died before reaching its own
appendLog(SESSION_LOG, "akm_index_failed", ...) line — the failure was
recorded nowhere in the plugin state dir, and every scripted run printed
`SessionEnd hook [...] failed: Hook cancelled` on stderr.

Spawn it detached + unref'd instead, matching sessionStart()'s reindex
and extractSession(). Following extractSession()'s precedent, the child's
stdout/stderr go to STATE_DIR/index.log rather than stdio: "ignore" so a
reindex that fails after the parent is gone still leaves a trace, and the
spawn attempt is recorded in session.log as `index_spawned` (a failed
spawn still logs `akm_index_failed`).

AKM_INDEX_TIMEOUT no longer has a consumer and is removed along with its
README row; index.log is documented in the state-file table.

Fixes #90

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018aDbzuoe77LGKFre2Dtj2k
@github-actions

Copy link
Copy Markdown
Contributor

Eval diff

  • Baseline SHA: 83de80205da91f6789ffeb9b063f8b89ed91f730
  • Candidate SHA: af3e23e8a293765965a8b0cdcea9b33ec381f2ff

Metric deltas

Metric Key Baseline Candidate Δ Δ% Status
curation mean_expected_coverage 0.9125 0.9125 0 0.00% • unchanged
curation mean_reciprocal_rank 0.9208 0.9208 0 0.00% • unchanged
curation zero_hit_rate 0.0500 0.0500 0 0.00% • unchanged
context_budget claude_violations 0 0 0 0.00% • unchanged
context_budget opencode_violations 0 0 0 0.00% • unchanged
context_budget claude_drop_rate 0 0 0 0.00% • unchanged
context_budget opencode_drop_rate 0 0 0 0.00% • unchanged
feedback claude_precision 1 1 0 0.00% • unchanged
feedback claude_recall 1 1 0 0.00% • unchanged
feedback claude_polarity_flips 0 0 0 0.00% • unchanged
feedback opencode_precision 1 1 0 0.00% • unchanged
feedback opencode_recall 1 1 0 0.00% • unchanged
feedback opencode_polarity_flips 0 0 0 0.00% • unchanged

Plugin surface

No surface changes.

No regressions.

@itlackey
itlackey merged commit bf07453 into main Aug 21, 2026
3 checks passed
@itlackey
itlackey deleted the fix/session-end-detached-reindex branch August 21, 2026 15:42
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.

Claude plugin: session-end hook blocks on akm index, gets cancelled by headless teardown, and never logs the failure

1 participant