Skip to content

fix(session): rebind on /clear-created new jsonl regardless of size (#856)#883

Merged
asheshgoplani merged 2 commits intomainfrom
fix/856-clear-rebind
May 6, 2026
Merged

fix(session): rebind on /clear-created new jsonl regardless of size (#856)#883
asheshgoplani merged 2 commits intomainfrom
fix/856-clear-rebind

Conversation

@asheshgoplani
Copy link
Copy Markdown
Owner

Closes #856.

Summary

The v1.7.23 size-guard from PR #664 (issue #661) uses a strict <= byte comparison in Instance.UpdateHookStatus (internal/session/instance.go). Candidate must have strictly more bytes than current to win a hook rebind. This stops the UserPromptSubmit flap where a fresh 1-record jsonl overwrites a 974KB historic jsonl — but it also rejects every user-initiated new session (/clear, plain claude, 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:

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.Time mirrors sessionConversationByteSize exactly — 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_RebindsRegardlessOfSize
Reproduces 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 (not reject) lifecycle event.

Preserved: All #661 protection tests still pass without modification:

  • TestInstance_UpdateHookStatus_RejectsRebindWhenCurrentHasMoreData
  • TestInstance_UpdateHookStatus_RejectsRebindWhenCurrentHasDataCandidateEmpty
  • TestInstance_UpdateHookStatus_AllowsRebindWhenCurrentIsEmpty
  • TestInstance_UpdateHookStatus_AllowsRebindWhenCurrentIsUnset
  • TestInstance_UpdateHookStatus_RejectsBidirectionalFlap
  • TestInstance_BuildClaudeResumeCommand_AfterFlap_ResumesRichID

The 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 passed
  • go test ./internal/session/ -count=1 -timeout 240s — all session tests pass
  • go test -run TestPersistence_ ./internal/session/... -race -count=1 — mandate from CLAUDE.md, passes
  • go build ./... — clean

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 asheshgoplani merged commit 086fa6d into main May 6, 2026
3 checks passed
asheshgoplani added a commit that referenced this pull request May 7, 2026
Three real-bug fixes:
- #856 size-guard rebinds on /clear (PR #883)
- #816 tmux SIGSEGV — adopted @tarekrached's clean-shutdown fix (PR #882)
- #881 profile divergence — unified TUI/web profile resolution (PR #884)

Committed by Ashesh Goplani
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.

Size-guard rejects new session after /clear until it outgrows the old one

1 participant