Repository navigation
fix(chat): hold pending input after process replacement - #1069
Merged
Merged
Conversation
Capture the agent process PID and birth time, plus OmO session identity, when pending input is queued and before it is delivered. Compare only identity parts known on both reads, with the existing macOS process-birth tolerance. Refs #540
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 1 minute. View limit details
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. Comment |
devswha
added a commit
that referenced
this pull request
Oct 17, 2026
…process (#1310) Refs #540 ## Root cause `pendingContext` (`server/index.ts`) checks a queued message's target with `samePendingIdentity`: agent, terminal, herdr's native session, foreground pid and birth, and OmO's session (#1069). For a Claude Code pane without herdr's hook, `agent_session` is null, and `/clear` or `/resume` switches the conversation inside the same process, so every part stays equal and the message is typed into the new conversation. ## What changed - Claude Code rewrites its own PID record, `<config>/sessions/<pid>.json`, when it switches session (checked in the 2.1.302 bundle: the concurrent-sessions writer updates `sessionId` on a session switch). `claudeForegroundSession` (`server/claude-store.ts`) reads it for the pane's one Claude process, through the existing `processClaudeConfigDir` and `claudeProcessSession` (which already verifies pid and process start). - `PendingIdentity` gains `claudeSession`; `samePendingIdentity` compares it only when both reads know it, as the other weak parts, so a session learned after enqueue is not a replacement and an unreadable record falls back to today's behavior. It is read for every Claude pane, hook or not. - No protocol or UI change. Changelog fragment `changelog.d/pending-claude-clear.md`. ## What is left on #540 - Other agents that report neither a native nor an OmO session and can switch session in one process (pi/omp, gjc, grok, hermes, OpenCode, Devin, Antigravity) are not covered: each would need its own session reading. - Windows panes where herdr reports no foreground process: no pid, so no PID record to read. That needs herdr to report the process. - A hooked Claude relies on herdr updating `agent_session` after `/clear`; `claudeSession` now covers it as well where the record is readable. ## Evidence - `server/pending-input.test.ts`: a known-and-different `claudeSession` is refused; equal, learned-later and unreadable-now are permitted (the refusal fails on origin/main, where the part is ignored). - `server/claude-store.test.ts`: `claudeForegroundSession` returns the recorded session, follows a rewrite of the record, and is null for no or two Claude processes. - `server/pending-input.contract.test.ts`: on an isolated herdr with a recorder named `claude` (no `agent_session`), a queued message is held with `pending_target_changed` and nothing is typed when the record names another session at the turn's end; the control delivers once when it keeps its session. The record itself is stubbed, so no real Claude store is written. ## Checks run - `bun run check fast`: all ok. - `bun run check run bun test --timeout 30000 ./server/pending-input.contract.test.ts` (isolated herdr): 63 pass, 0 fail. - Browser lane not run locally; CI runs it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.
Refs #540
Root cause
pendingContextinserver/index.ts(origin/main L787-788) judged whether a queued message still targets the same agent by comparing onlyagent,terminalIdand the nativesessionreference. An agent that reports no native session (hookless) and is killed and restarted in the same pane keeps the same terminal and agent name, so the replacement process received the message typed for the old one. That comparison was also strict onsession, so a session that was unknown at enqueue and became known later was refused as if the target had changed.What changed
server/pending-input.ts:PendingIdentitygains optional weak partsprocessPid,processBirthandomoSession. The newsamePendingIdentity()keeps strict equality foragentandterminalId. Every other part is compared only when both reads know it, so unknown to known is an upgrade, not a replacement, while known to different is refused. Process birth follows the existinggjc-runtime.tsrule: exact on Linux, ±1000 ms on macOS, whereps etimehas whole-second precision.server/index.tspendingContext: the identity is enriched with the foreground pid from the typedpaneProcessInfowrapper, its birth from the existingprocessStartedAthelper, andOmoStatus.sessionOf(paneId). It is captured at enqueue and compared on every delivery check. A mismatch raisespending_target_changed, so the message is held for review. Parts learned later are kept on the stored identity, so a replacement between paste and Enter is checked against them too.changelog.d/pending-process-identity.md.Residual (why Refs, not Fixes)
A
/clearor resume inside the same process, by an agent that reports neither a native session nor an OmO session, is still indistinguishable, so it is out of scope. On Windows panes where herdr reports no foreground process, the fingerprint stays weak (pid/birth unknown) and the previous behavior applies.Evidence
Each new assertion is labelled
regressionorcontrolin the tests.samePendingIdentitydoesn't exist there.heldwithpending_target_changed.foreground_processes[0]stays the agent while it runs a child in its own foreground process group. A tool subprocess therefore does not flip the fingerprint.Checks run
bun run typecheck: exit 0HERDR_TEST_MODE=unit bun test ./server/pending-input.test.ts: 29 pass, 0 failbun test ./server/pending-input.contract.test.ts(isolated herdr session): 56 pass, 0 failbun run test:unit: 3518 pass, 0 fail, 8 skippedgit diff --check: clean