fix(session): rebind on /clear-created new jsonl regardless of size (#856)#883
Merged
asheshgoplani merged 2 commits intomainfrom May 6, 2026
Merged
fix(session): rebind on /clear-created new jsonl regardless of size (#856)#883asheshgoplani merged 2 commits intomainfrom
asheshgoplani merged 2 commits intomainfrom
Conversation
Five PNG diagrams in documentation/assets/ embedded into the matching guides. Generated via codex CLI's built-in image_gen tool (gpt-image-2), Tokyo Night palette, technical-architecture style. - conductor-overview.png → CONDUCTOR.md hero - channels-topology.png → CONDUCTOR.md (one-bot-per-conductor) - watcher-doorbell.png → WATCHERS.md hero - skills-tiers.png → SKILLS.md hero - watchdog-restart.png → WATCHDOG.md hero Total ~5.6MB; each image roughly 1MB. PNG-only (gpt-image-2 doesn't emit SVG). Recipe to regenerate is in ~/.agent-deck/conductor/CLAUDE.md in case any concept evolves and the diagrams need refreshing. No code changes.
…856) The v1.7.23 size-guard added in PR #664 (issue #661) used a strict `<=` byte comparison: candidate must have strictly more bytes than current to win a hook rebind. This stops the UserPromptSubmit flap that lets a fresh 1-record jsonl overwrite a 974KB historic jsonl, but also rejects every user-initiated new session — `/clear`, plain `claude`, picker `--resume` — since fresh sessions are smaller by definition. The tile stays bound to the old uuid until the new jsonl outgrows it in bytes (~209KB in the real-install evidence). Fix: add an mtime-gap escape hatch. In a #661 flap the user keeps typing into the rich session, so its mtime stays fresh; in /clear the user abandons the old session, so its mtime stales while the new jsonl's mtime advances. When candidate.mtime - current.mtime >= 5s (clearRebindMtimeGrace), treat the candidate as a user-initiated new session and rebind regardless of size. 5s is well above the ~2s hook poll cadence (flaps touch both files inside that window) but well below the time a /clear + follow-up prompt takes. The new helper sessionConversationMtime mirrors sessionConversationByteSize exactly — same per-instance config-dir lookup, same all-projects fallback, same graceful degradation to zero on missing file. Coverage: - TestInstance_UpdateHookStatus_ClearCreatesNewSession_RebindsRegardlessOfSize reproduces the issue: 200KB old jsonl with 2-min-old mtime, 1-record new jsonl with current mtime → must rebind to new id. - All existing #661 protection tests still pass: flap test seeds both files within microseconds, so mtime gap is well below 5s and the size guard remains the deciding factor. Committed by Ashesh Goplani
asheshgoplani
added a commit
that referenced
this pull request
May 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #856.
Summary
The v1.7.23 size-guard from PR #664 (issue #661) uses a strict
<=byte comparison inInstance.UpdateHookStatus(internal/session/instance.go). Candidate must have strictly more bytes than current to win a hook rebind. This stops theUserPromptSubmitflap where a fresh 1-record jsonl overwrites a 974KB historic jsonl — but it also rejects every user-initiated new session (/clear, plainclaude, picker--resume). Fresh sessions are smaller by definition, so the tile stays bound to the old uuid until the new jsonl outgrows it in bytes (~209KB in the real-install evidence in #856).Fix
Add an mtime-gap escape hatch. The discriminator between a flap and a user-initiated new session is mtime:
/clearuntil it outgrows the old one #856): user abandons the old session → its mtime stales; the new jsonl's mtime advances as Claude writes to it.When
candidate.mtime - current.mtime >= clearRebindMtimeGrace(5 s), treat the candidate as a user-initiated new session and rebind regardless of size. 5 s is well above the ~2 s hook poll cadence (flaps touch both files inside that window) but well below the time a/clear+ follow-up prompt takes.New helper
sessionConversationMtime(inst, sessionID) time.TimemirrorssessionConversationByteSizeexactly — same per-instance config-dir lookup, same all-projects fallback, same graceful degradation to the zero time on missing file.Test coverage
Added:
TestInstance_UpdateHookStatus_ClearCreatesNewSession_RebindsRegardlessOfSizeReproduces the issue: 200 KB old jsonl with 2-min-old mtime, 1-record new jsonl with current mtime → must rebind to new id and emit a
rebind(notreject) lifecycle event.Preserved: All #661 protection tests still pass without modification:
TestInstance_UpdateHookStatus_RejectsRebindWhenCurrentHasMoreDataTestInstance_UpdateHookStatus_RejectsRebindWhenCurrentHasDataCandidateEmptyTestInstance_UpdateHookStatus_AllowsRebindWhenCurrentIsEmptyTestInstance_UpdateHookStatus_AllowsRebindWhenCurrentIsUnsetTestInstance_UpdateHookStatus_RejectsBidirectionalFlapTestInstance_BuildClaudeResumeCommand_AfterFlap_ResumesRichIDThe flap tests seed both files within microseconds, so the mtime gap is well below 5 s and the size guard remains the deciding factor — none of #661's protection regresses.
Test plan
go test -run "TestInstance_UpdateHookStatus_(Clear|Rejects|Allows)|TestInstance_BuildClaudeResumeCommand_AfterFlap" ./internal/session/ -count=1— 7 passedgo test ./internal/session/ -count=1 -timeout 240s— all session tests passgo test -run TestPersistence_ ./internal/session/... -race -count=1— mandate from CLAUDE.md, passesgo build ./...— clean