refactor(coding-agent): remove the test-only daemon-lookup seam from main.ts - #1824
refactor(coding-agent): remove the test-only daemon-lookup seam from main.ts#1824snimu wants to merge 108 commits into
Conversation
…onfig-cache-reset
…flow-patterns-export
…e-daemon-lookup-fake
…onfig-cache-reset
…flow-patterns-export
…e-daemon-lookup-fake
…ot-dispose-timeout
…onfig-cache-reset
…flow-patterns-export
…e-daemon-lookup-fake
| /** Best-effort final snapshot before a graceful dispose, bounded by a timeout. */ | ||
| private async flushSnapshotForDispose(): Promise<void> { | ||
| if (!this.options.snapshot || !this.isRunning) return; | ||
| const pendingExecutions = this.executionQueue; |
There was a problem hiding this comment.
🟠 High kernel/repl-manager.ts:1104
dispose() and shutdown({ snapshot: true }) can wait indefinitely instead of honoring SNAPSHOT_EXECUTION_TIMEOUT_MS. flushSnapshotForDispose() captures executionQueue before waiting, so a concurrent execute() can enqueue a non-terminating request afterward; captureSnapshot() queues behind it, and its timeout does not start until that request finishes. Reserve the queue before awaiting it (or otherwise block new executions) so the final snapshot cannot be overtaken.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/core/kernel/repl-manager.ts around line 1104:
`dispose()` and `shutdown({ snapshot: true })` can wait indefinitely instead of honoring `SNAPSHOT_EXECUTION_TIMEOUT_MS`. `flushSnapshotForDispose()` captures `executionQueue` before waiting, so a concurrent `execute()` can enqueue a non-terminating request afterward; `captureSnapshot()` queues behind it, and its timeout does not start until that request finishes. Reserve the queue before awaiting it (or otherwise block new executions) so the final snapshot cannot be overtaken.
Evidence trail:
packages/coding-agent/src/core/kernel/repl-manager.ts:434-450, 453-497, 897-910, 1002-1020, 1102-1117 at REVIEWED_COMMIT
| // The session transition and transcript are already authoritative here; | ||
| // a transient queue read must not turn a successful switch into a fatal error. | ||
| await this.refreshConnectionQueue().catch(() => undefined); | ||
| this.updatePendingMessagesDisplay(); |
There was a problem hiding this comment.
🟡 Medium interactive/interactive-mode.ts:2885
During session replacement, a queue update from another client can be overwritten by the stale state returned from getInitialSnapshot(), leaving pending previews and queue-edit indices/text stale until another queue event arrives; subsequent edits are then rejected. renderCurrentSessionState() should refresh the authoritative queue after rendering the snapshot instead of only calling updatePendingMessagesDisplay().
- this.updatePendingMessagesDisplay();
+ await this.refreshConnectionQueue().catch(() => undefined);🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/modes/interactive/interactive-mode.ts around line 2885:
During session replacement, a queue update from another client can be overwritten by the stale state returned from `getInitialSnapshot()`, leaving pending previews and queue-edit indices/text stale until another queue event arrives; subsequent edits are then rejected. `renderCurrentSessionState()` should refresh the authoritative queue after rendering the snapshot instead of only calling `updatePendingMessagesDisplay()`.
Evidence trail:
packages/coding-agent/src/modes/interactive/interactive-mode.ts:2878-2887, 5060-5111, 6555-6589, 2580-2600, 2640-2661, 7385-7423 at e29065245ef3209669ab7e64c3f478a8daf94d8e; git diff MERGE_BASE..REVIEWED_COMMIT -- packages/coding-agent/src/modes/interactive/interactive-mode.ts
There was a problem hiding this comment.
🟡 Medium
A stale rejected queue mutation leaves queueSelection pointed at the removed or moved item, so the next Enter/Alt+Enter retries the same obsolete (lane, index, text) tuple until the user exits browse mode. Reset the selection when restoring a rejected edit so subsequent submissions operate on the current editor draft.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/modes/interactive/interactive-mode.ts around line 7127:
A stale `rejected` queue mutation leaves `queueSelection` pointed at the removed or moved item, so the next Enter/Alt+Enter retries the same obsolete `(lane, index, text)` tuple until the user exits browse mode. Reset the selection when restoring a rejected edit so subsequent submissions operate on the current editor draft.
Evidence trail:
packages/coding-agent/src/modes/interactive/interactive-mode.ts:2640-2645, 7057-7061, 7086-7138 at e29065245ef3209669ab7e64c3f478a8daf94d8e
packages/coding-agent/src/modes/interactive/queue-selection.ts:24-30, 76-83 at e29065245ef3209669ab7e64c3f478a8daf94d8e
packages/coding-agent/src/core/agent-session.ts:6371-6404 at e29065245ef3209669ab7e64c3f478a8daf94d8e
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 2e87f0f. Configure here.
| if ( | ||
| this.connectionQueue === queueBefore && | ||
| lane[selected.index] === selected.text && | ||
| target >= 0 && |
There was a problem hiding this comment.
Queue browse lost after move race
Medium Severity
A successful queue reorder can still drop browse mode when the mutation response arrives before session_action_update. refreshQueueSelectionAt then reads the stale snapshot at the predicted index, treats the text mismatch as a lost item, and restores the draft. pendingQueueMove also suppresses the late event from repairing selection, so chained moves/edits no-op even though the server applied the reorder.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 2e87f0f. Configure here.
|
Superseded: the chained stack was restructured into independent PRs (byte-identical combined tree). A fresh standalone PR for this change follows on the same branch name. |


What was wrong
main.ts's interactive-startup wrapper accepted an optional injected daemon-lookup function that only three unit tests ever passed — a test-only seam in production code. Those tests asserted the wrapper's own try/catch branches through the fake, i.e. they restated the implementation (audit: test-only production code, test-only.md finding 5).The fix
+2/−48: the lookup type and option are deleted,
findActiveDaemonSessionSummaryis called directly, the three fake-lookup tests and their import are gone, and the function's now-unimportedexportkeyword is dropped. The realfallbackOnErrorpolicy and its production caller are byte-identical.How it's verified
Reviewer confirmed no other caller passed the option, the deleted tests covered no user-relevant obligation, and the remaining 52 routing tests pass. tsgo/biome clean; full CI-style suite failure set identical to stack base (two unrelated full-load flakes pass focused on both branches). Two-model implement/review loop, approved first pass.
Stacked on #1737 (test the whole stack at the leaf; merge base-first).
Note: intentionally no Linear ticket for this cleanup stack, so that check stays red.
Note
Medium Risk
Changes agent turn timing, compaction continuation, cross-daemon messaging, and RPC timeout defaults in core session/daemon paths; telemetry may run in test unless explicitly disabled.
Overview
This PR tightens coding-agent runtime and TUI behavior while stripping test-only APIs and several correctness bugs around daemons, compaction, and child agents.
Interactive queue editing no longer mirrors the queue in
connectionQueue. The TUI readsconnectionState.sessionActionsand revalidates the selected entry withQueueSelection.refreshAtafter mutations orsession_action_updateevents, so duplicate prompts and optimistic double-patches are avoided.RpcClientdrops default request timeouts (including refine); long turns wait until idle unless callers pass an explicit timeout, and transport/process death fails all pending waits.AgentSessionremoves the post-compaction 100ms delay and runs continuation behind idle/retry/refine gates, optional session-input draining, and commit fences. RLM child snapshots are built in-session (activity, previews, retained runs) and the daemon attach path mapsactiveSessionIdfrom resident children. Legacyrlm-subagents.jsonlentries withoutsessionDirhydrate viareadLegacyRlmSubagentRegistry. Kernel graceful dispose waits on the execution queue (bounded bySNAPSHOT_EXECUTION_TIMEOUT_MS) before a final snapshot instead of racing a dispose timeout.Daemon/worker fixes: remote
send_messageconnects once and does not resend after a post-send disconnect; supervised renames on workers apply names without a second availability check; passivation no longer re-registers children on failed close.looksLikeSessionPathis shared fromsession-resolver. Removed: injectable daemon lookup inmain,clearConfigValueCache,getOverflowPatterns, and implicitNODE_ENV=testtelemetry opt-out (tests should useDO_NOT_TRACK/ env overrides).Reviewed by Cursor Bugbot for commit 2e87f0f. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Remove test-only seams and rework queue, compaction, RPC in
coding-agentfindActiveDaemonSessionSummaryForInteractiveStartuplookup override in main.ts,clearConfigValueCache/clearApiKeyCachein resolve-config-value.ts and model-registry.ts, and theNODE_ENV === 'test'telemetry opt-out in telemetry.tsInteractiveModeto derive the connection queue fromconnectionState.sessionActionsviagetConnectionQueue()instead of maintaining a local mirror; move and edit flows now consult authoritative state and avoid optimistic local patching (interactive-mode.ts, queue-selection.ts)AgentSession._runScheduledPostCompactionContinue; the method now coordinates with queued work, compaction races, and ownership without a fixed timer (agent-session.ts)RpcClientto propagate transport/process failures to all pending operations viatransportError, removes per-request timeouts fromsend(), and makeswaitForIdle/promptAndWait/collectEventscancellable with optional timeouts (rpc-client.ts)RpcClient.send()no longer applies a default timeout; long-running commands wait indefinitely unless the caller passes an explicit timeout.InteractiveModeno longer optimistically patches the queue mirror, so UI updates depend on server responses.isTelemetryEnabledmay return true during tests unlessDO_NOT_TRACK=1is set (now added to vitest.config.ts)Macroscope summarized 2e87f0f.
Linear ticket: ENG-5658
(ticket linked above)
Supersedes #1738 (recreated as a plain PR against
main; GitHub's stack lock prevented retargeting the stacked PR).