Skip to content

fix(claude-sdk-oauth): ignore volatile top hooks in continuity hashes - #1085

Open
codeg-dev wants to merge 4 commits into
code-yeongyu:mainfrom
codeg-dev:fix/session-sync-volatile-hook-hashes
Open

fix(claude-sdk-oauth): ignore volatile top hooks in continuity hashes#1085
codeg-dev wants to merge 4 commits into
code-yeongyu:mainfrom
codeg-dev:fix/session-sync-volatile-hook-hashes

Conversation

@codeg-dev

@codeg-dev codeg-dev commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-on to #791. Top-of-turn hook injections (omo-memory notice, goal-continuation, mindy-team context blocks, senpi-task usage) convert to user-role bodies. Hashing those bodies makes a rewrite or prepend look like sent_stream_diverged, so decideFromBinding flattens to a cold seed and burns the Anthropic prompt cache even when the real conversation prefix is intact.

This is complementary to #1075 (rolling cache checkpoints after serialize). #1075 cannot help if continuity flattens the session first.

Evidence

  • Stock RED: notice rewrite or prepend -> flatten / sent_stream_diverged.
  • GREEN: bun x vitest --run packages/coding-agent/test/suite/regressions/claude-sdk-oauth-volatile-hook-continuity.test.ts packages/coding-agent/test/suite/regressions/790-claude-sdk-oauth-empty-user-continuity.test.ts (9 passed).

Test plan


Summary by cubic

Prevents false session divergence in claude-sdk-oauth by excluding volatile top-of-turn hook content from continuity hashes. Previously hashing converted hook bodies triggered sent_stream_diverged and a flatten; now hooks are hashed by kind while staying on the wire, so only real user edits diverge.

  • convertToLlm stamps hook customType into request-local provenance; content signatures keep compatibility with already-converted hooks.
  • Covers memory notices, goal continuations, mindy-team context blocks, and senpi-task, monitor, wake, terminal, directive, and skill-pointer hooks.
  • Genuine user rewrites and structural prepends still diverge. Regression tests added.

Written for commit 52b4843. Summary will update on new commits.

Review in cubic

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9aab0085ad

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@codeg-dev

Copy link
Copy Markdown
Contributor Author

P1 accepted. isTransmittedMessage is restored to the previous contract (content-less user messages only). Hook rewrite is neutralized inside sentMessageHashes as { role, volatileHook: kind } so the slot stays in messages.slice(from) and a goal-continuation-only turn still reaches the SDK.

Structural prepend still reports sent_stream_diverged (fail-closed). Hook content rewrite reattaches.

Pushed on this branch (3fd8bde). Tests: claude-sdk-oauth-volatile-hook-continuity + #790 still 9/9.

@code-yeongyu

Copy link
Copy Markdown
Owner

Thanks @codeg-dev — the core idea still matters and is not superseded by today's main (6dd662c4b: #1318 unconfirmed bindings, #1311 content-blocks, #1312 is_error, #1310 SDK 0.3.259, #1309 OAuth slot). Hashing converted hook bodies in sentMessageHashes can still false-flatten a rewrite (omo-memory notices especially). isTransmittedMessage is the wrong seam for this; commit 3fd8bde already got that right — keep hooks in the transmitted list so buildDeltaPromptBlocks(messages.slice(from)) can send a hook-only turn.

Remaining work before this can merge:

  1. Rebase onto main (6dd662c4b). session-sync.ts should apply clean around sentMessageHashes; do not touch appendContent / content-blocks.ts. Resolve changes.md by prepending a new dated entry at the top (do not let it land next to the 2026-08-21 settings-cache section).
  2. Make detection work on the live list: convertToLlm drops customType (messages.ts custom arm). Either stamp customType into __piContextProvenance at convert time, or add content signatures for monitor/wake/terminal. As written, five of nine VOLATILE_HOOK_CUSTOM_TYPES never match session-stream.ts hashes.
  3. Re-scope kinds: goal-continuation is append-only (messages.ts); hashing it by kind is optional and fail-open. Keep memory-notice / RULES / senpi-task prefixes if those still rewrite in place.
  4. Replace the tautological continuity fixtures. Assert sentMessageHashes on convertToLlm output (or provenance-stamped user messages). Do not plant customType on already-user-role objects. Set sdkSessionIdConfirmed: true on reattach bindings (fix(claude-sdk-oauth): never resume an SDK session id that init never confirmed #1318). Keep a real user rewrite and a structural prepend as fail-closed cases. Keep claude-sdk-oauth: a content-less user message forces a full-history re-send every time it disappears #790 empty-user coverage.
  5. Add a packages/coding-agent/CHANGELOG.md [Unreleased] ### Fixed bullet (runtime source; no no-changelog label). Tracker entry already has the four canonical sections if it names session-sync.ts.
  6. After rebase, CI must actually run (required: Check and test, Changelog gate, Review claim gate).

Suggested tests (not run in this triage):
bunx vitest run packages/coding-agent/test/suite/regressions/claude-sdk-oauth-volatile-hook-continuity.test.ts packages/coding-agent/test/suite/regressions/790-claude-sdk-oauth-empty-user-continuity.test.ts
plus whatever session-sync / session-continuity regressions main now has. Changelog: bun scripts/check-pr-changelog.mjs with CHANGELOG_GATE_BASE set to the rebase base.

codeg-dev and others added 4 commits September 3, 2026 15:07
Top-of-turn injections (memory notice, goal continuation, rule/task
blocks) convert to user-role bodies. Hashing them makes a rewrite or
prepend look like sent_stream_diverged and flatten to a cold seed.
Exclude them from isTransmittedMessage the same way content-less user
messages are excluded (PR code-yeongyu#791). Genuine user rewrites stay fail-closed.
…wire

Review P1: isTransmittedMessage also feeds buildDeltaPromptBlocks(messages.slice(from)).
Dropping hooks there would omit a goal-continuation-only turn. Neutralize hook
content inside sentMessageHashes instead so rewrite does not flatten, while
from still indexes the full transmitted list.
…tile hook types

Co-authored-by: Code_G <288527233+codeg-dev@users.noreply.github.com>
Signed-off-by: Code_G <288527233+codeg-dev@users.noreply.github.com>
@codeg-dev
codeg-dev force-pushed the fix/session-sync-volatile-hook-hashes branch from cf0e5d8 to 52b4843 Compare September 3, 2026 06:18
@codeg-dev

Copy link
Copy Markdown
Contributor Author

Updated the branch against current main and addressed the remaining continuity review points.

  • convertToLlm now preserves custom-message type in request-local provenance, so the live converted list identifies monitor/wake/terminal/directive hooks without planting customType on user-role fixtures.
  • Rewrite-in-place hooks stay on the wire and hash by kind; append-only goal-continuation remains content-significant.
  • Reattach fixtures now set sdkSessionIdConfirmed: true; structural prepend and real user rewrites remain fail-closed.
  • Added the coding-agent [Unreleased] changelog entry and updated both applicable changes.md trackers.

Local verification:

  • 4 continuity test files: 32 passed
  • npm run check: passed
  • changelog gate against current rebase base: passed
  • hermetic anthropic-format mock-loop smoke: passed

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