Fix App Control shell hijacking chat CLI terminal routing#371
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
@copilot review but do not make fixes |
Reviewed the latest commit set for correctness/regression risk only, and I did not find any actionable issues. I did not apply any fixes. I also attempted targeted validation ( |
There was a problem hiding this comment.
PR Review
Scope: 5 file(s), +263 / −43
Verdict: Minor issues
This PR splits chat-scoped PTY routing into separate chat-CLI and auxiliary active maps so App Control shells no longer hijack activeForChat / reattachChatCli, while terminal.read / write / signal keep targeting auxiliary shells. It also wraps ade/actions/call in operation audit bookkeeping. The routing change is well covered by new tests; the audit wrapper has one accuracy gap.
🐛 Functionality
[Medium] Failed run_ade_action results can be audited as succeeded
File: apps/ade-cli/src/adeRpcServer.ts:L5262-L5268
Issue: callActionWithAudit always calls operationService.finish with status: "succeeded" when callAction returns without throwing. run_ade_action (and other tools) often return domain failures inside the payload instead of throwing, so operations can be recorded as successful even when the invoked action failed.
Repro: Call ade/actions/call with name: "run_ade_action" for a domain action that returns { ok: false, error: "..." } (or similar) without throwing; observe operationService.finish gets status: "succeeded" and resultStatus: "success".
Fix: Before finishing, inspect the return value (for run_ade_action, check result / nested ok flags) and use status: "failed" with an error patch when the payload indicates failure; only use "succeeded" when the action actually succeeded.
Notes
- The dual-map routing in
ptyService.tsmatches the updatedpty-and-processes.mdcontract and the new regression tests (chat CLI preferred foractiveForChat/reattachChatCli, auxiliary forread/write/signal) look like the right fix for the hijacking bug. - Callers that used
activeForChatto surface auxiliary output (e.g. dev-server logs) while a chat CLI is also live may now correctly get the chat CLI; if that UX matters, a follow-up API or flag is cleaner than overloadingactiveForChat.
Sent by Cursor Automation: BUGBOT in Versic
|
@copilot review but do not make fixes |
|
Capy auto-review is paused for this organization because the monthly auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews. |
2a7ffff to
2854dd4
Compare
|
@copilot review but do not make fixes |
App Control launches a shell PTY with the parent chatSessionId for sidebar nesting, but that overwrote activeTerminalByChatSession and caused activeForChat/reattachChatCli to target the Electron dev shell instead of the agent CLI — sending resume commands and signals to the wrong process. Partition routing: chat-CLI sessions own the active map; auxiliary shells are resolved separately for read/write/list vs activeForChat/reattach. Co-authored-by: Arul Sharma <arul28@users.noreply.github.com>
2854dd4 to
f0dd4bf
Compare
|
@copilot review but do not make fixes |


Bug and impact
App Control shell steals chat CLI terminal routing (significant breakage)
When App Control is launched from a chat, it creates a
toolType: "shell"PTY with the parentchatSessionId(for sidebar nesting). That PTY was registered inactiveTerminalByChatSession, overwriting the agent CLI binding. SubsequentactiveForChat,reattachChatCli, andresolveTerminalIdcalls could target the App Control / dev-server shell instead of the Claude/Codex/Cursor CLI.Concrete trigger: Open a chat with a live agent CLI, launch App Control from that chat, then use "Use ADE terminal" (Codex) or any path that calls
activeForChat+terminal.write— the resume command is injected into the Electron/npm shell, not the agent CLI. Ctrl+C and signals can kill the controlled app.Root cause
activeTerminalByChatSessiontreated every PTY with achatSessionIdequally. App Control intentionally setschatSessionIdfor UI nesting, but must not become the canonical chat-CLI routing target.Fix
claude-chat,codex-chat, etc.) vs auxiliary (shell, App Control).activeTerminalByChatSession.activeForChat/reattachChatCliprefer chat-CLI; auxiliary shells are a fallback for log/reveal flows.resolveTerminalId(read/write/preview) routes only to auxiliary terminals.Validation
vitest run src/main/services/pty/ptyService.test.ts -t "chat terminal contract"(22 tests)prefers chat CLI over a newer App Control shell with the same chatSessionIdDoes not overlap open draft PRs #363–#367 (sync/CRR/orchestration/binding fixes).
Greptile Summary
This PR fixes a routing conflict where App Control shells (with
toolType: "shell") were overwriting the active chat-CLI entry inactiveTerminalByChatSession, causing agent resume commands to be injected into dev-server PTYs instead of the Claude/Codex CLI.activeAuxiliaryTerminalByChatSession, and a set of predicate helpers (isChatCliRoutingEntry,isAuxiliaryRoutingEntry) to partition PTYs by role: onlyclaude-chat,codex-chat,cursor,opencode-chat, anddroid-chatsessions own the chat-CLI route, while shells and App Control entries own the auxiliary route.resolveTerminalId(used byreadTerminal,writeTerminal,signalTerminal) now targets only the auxiliary map, so tool-call writes tochatSessionIdalways land in the App Control shell rather than the agent CLI;activeForChatandreattachChatClipreference chat-CLI and fall back to auxiliary only for display/log flows.Confidence Score: 5/5
The routing split is well-scoped and the two-map invariant is maintained at every creation, disposal, and lookup site.
All write/promote paths use toolTypeHint from the in-memory PTY entry rather than the session-service field, avoiding the field-mismatch pitfall. Disposal handlers correctly guard on map-ownership before evicting, and activeEntryFromMap self-heals stale entries. The regression tests cover both the preference ordering and the per-operation routing split.
No files require special attention.
Important Files Changed
Sequence Diagram
sequenceDiagram participant Caller participant ptyService participant chatCliMap as activeTerminalByChatSession participant auxMap as activeAuxiliaryTerminalByChatSession participant AppCtrl as App Control PTY (shell) participant ChatCLI as Chat CLI PTY (claude-chat) Note over ptyService: PTY Creation - partitioned routing Caller->>ptyService: "create({ toolType: claude-chat, chatSessionId })" ptyService->>chatCliMap: set(chatSessionId, chatCli.sessionId) Caller->>ptyService: "create({ toolType: shell, chatSessionId })" ptyService->>auxMap: set(chatSessionId, appCtrl.sessionId) Note over ptyService: activeForChat - prefers chat-CLI Caller->>ptyService: "activeForChat({ chatSessionId })" ptyService->>chatCliMap: activeChatCliEntryFor(chatSessionId) ptyService-->>Caller: ChatTerminalSession (chatCli) Note over ptyService: resolveTerminalId - auxiliary only Caller->>ptyService: "writeTerminal({ chatSessionId, data })" ptyService->>auxMap: activeAuxiliaryEntryFor(chatSessionId) ptyService->>AppCtrl: pty.write(data) Note over ChatCLI: chatPty.write NOT called ptyService-->>Caller: ok: trueReviews (4): Last reviewed commit: "ship: align action audit with main" | Re-trigger Greptile