Residual work from the two CTO PRs merged on 2026-07-31: #986 (aa725484a — lane isolation, attention badge, seeded intro turn) and #987 (774980ff0 — live operator-tool registration, mutation lane guard, iOS attention transport).
Everything here was found and consciously deferred, not discovered later. Ordered by what I'd actually do first.
1. Nothing in either PR was verified in a running app · highest residual risk
Diagnosis. All verification was typecheck + lint + unit tests. The tests mock the provider SDKs at precisely the boundary #987 changed, so the headline claim — "the CTO's operator tools actually execute now" — is proven by construction, not observation. Before #987, createCtoOperatorTools was only ever reached by previewSessionToolNames and the prompt manifest; the bodies never ran. It now feeds five transports (Claude SDK MCP ade-cto, Codex ade_cto dynamic tools, HTTP MCP lease for Cursor/Droid/OpenCode) and none of those paths has been exercised against a real SDK.
An attempt to attach to the running app early in the session failed (no CDP port) and was never retried.
Proof plan — launch the dev app under App Control and capture:
- a CTO session executing one operator tool end-to-end (
listLanes is the cheapest read)
spawnChat with no laneId landing on a freshly created lane, not the primary worktree — this is the original bug and the one most worth seeing
- a mutating git tool called without
laneId returning the guard error instead of touching primary
- the CTO tab dot lighting when the thread asks a question and clearing when answered
- first-run: onboarding → seeded visible intro turn appears
Attach via ade proof capture. Repeat at minimum on Claude and Codex, since those two use completely different registration mechanisms (SDK MCP server vs. dynamic-tool protocol).
2. getCtoAttention false-clears the badge, and its comment says the opposite · Low, but the comment is wrong today
Diagnosis. apps/desktop/src/main/services/chat/agentChatService.ts:38310. On a throw from listIdentitySessions (transient KV/disk error during per-row summarize) the probe returns idle. The comment at :38320 claims "the renderer keeps its last known state rather than falsely clearing a pending question" — but returning idle is a false clear. The comment is actively misleading.
Impact is genuinely small: only the badge goes dark, the question is still in the thread, and the probe self-heals on the next cycle (15s visible-tab interval, chat events, focus). A persistent failure means sessionService.list() is broken and the Work tab is down too.
Transport failures do keep the last value (desktop hook and iOS both swallow and retain) — so the two failure modes differ, which is the actual bug. Fixing it properly means an explicit unknown in CtoAttentionState threaded through all three transports (IPC, cto_state action, cto.getAttention sync command) plus iOS.
Minimum: correct the comment. Proper: add the third state.
3. Review coverage on both PRs was effectively nil
Diagnosis. #986: Codex and Greptile never triggered; CodeRabbit rate-limited. #987: CodeRabbit landed 2 comments (both already fixed by the /quality pass — it independently found the same iOS roster-signature bug) then hit its limit; Codex was out of credits. ~2,400 lines touching all five runtime tool transports merged with only self-review plus the two /quality tracks.
Not a code defect — a coverage fact worth knowing when triaging anything that surfaces in this area. Consider a re-review pass on 774980ff0 once bot quota resets.
4. CtoOperatorToolDeps.ptyService still restates PtyCreateArgs lossily
apps/desktop/src/main/services/ai/tools/ctoOperatorTools.ts:64. Declares cols?/rows?/title? optional where the real PtyCreateArgs requires them; the mismatch is silenced by an as cast in buildCtoOperatorToolDeps. #987 fixed the symptom (createTerminal now passes explicit cols: 100, rows: 30, title) but not the contract — the cast is still load-bearing and will hide the next drift. Fix: import PtyCreateArgs instead of structurally restating it, then delete the cast.
5. CTO context pressure — unmeasured
~80 operator tools are now registered alwaysLoad: true on every CTO session, and CTO sessions run with ENABLE_TOOL_SEARCH=0 (agentChatService.ts, pre-existing). Their schemas now enter context on top of the prompt manifest that already documents them — the manifest is generated from the same tool set, so there is real redundancy. Never measured. Worth a token count on a live CTO session before deciding whether to drop alwaysLoad or re-enable tool search.
6. Dead code from the /quality restructure
ensureOrchestrationHttpMcpServer and ensureCtoHttpMcpServer (agentChatService.ts:15521, :15525) are thin wrappers with zero remaining callers — everything goes through ensureHttpMcpServer / ensureHttpMcpLeases directly. Delete.
7. Pre-existing repo failures (not from these PRs, but real)
Both reproduce on a clean tree and were verified against origin/main:
agentChatService.test.ts > passes discovered local Claude plugins to SDK sessions — picks up /Applications/ADE.app/Contents/Resources/agent-skills from the installed app, so it fails locally for anyone with ADE installed and passes in CI. Should be made environment-independent.
syncHostService.test.ts > resubscribes chat listeners after reconnect and routes chat remote commands
- 9 typecheck errors in
agentChatService.ts from SDK type drift (SDKControlInterruptResponse.cancelled, RewindFilesResult.skippedLinks)
8. Judgment calls — recorded so they are not re-litigated silently
- Universal search still indexes the CTO thread. Deliberate: it is your own conversation and should be findable in ⌘K, even though the thread is hidden from every roster.
- Dock badge can lag the CTO tab dot by ~2.5s. Declined during
/quality — the fix costs a ref mirror plus a second effect, and the single-writer property for setDockBadgeCount is worth more than the lag.
- The four newly-wired tools (
steerChat, cancelSteer, listSubagents, approveToolUse) are not exposed through the CLI's curated 14-name MCP subset. Product call, consistent with that subset already omitting the git/PR families.
main/services/cto/ sits at 4 test files against a nominal budget of 3. Forcing 3 means merging two untouched Linear suites (~225 assertions) — unrelated churn with real regression risk.
Terminology note for whoever picks this up
The CTO does not have its own lane. Its session is pinned to the project's primary lane (resolvePrimaryIdentityLane, which throws if no primary exists). Earlier phrasing in this work said "the CTO's own lane", which reads as though it has a private workspace — the opposite of the truth, and the reason the original bug (always default laneId to the CTO's current lane) was so easy to miss. The docs were corrected; if you find that phrasing anywhere, it is stale.
Filed from the ADE Work session that shipped #986 and #987.
Residual work from the two CTO PRs merged on 2026-07-31: #986 (
aa725484a— lane isolation, attention badge, seeded intro turn) and #987 (774980ff0— live operator-tool registration, mutation lane guard, iOS attention transport).Everything here was found and consciously deferred, not discovered later. Ordered by what I'd actually do first.
1. Nothing in either PR was verified in a running app · highest residual risk
Diagnosis. All verification was typecheck + lint + unit tests. The tests mock the provider SDKs at precisely the boundary #987 changed, so the headline claim — "the CTO's operator tools actually execute now" — is proven by construction, not observation. Before #987,
createCtoOperatorToolswas only ever reached bypreviewSessionToolNamesand the prompt manifest; the bodies never ran. It now feeds five transports (Claude SDK MCPade-cto, Codexade_ctodynamic tools, HTTP MCP lease for Cursor/Droid/OpenCode) and none of those paths has been exercised against a real SDK.An attempt to attach to the running app early in the session failed (no CDP port) and was never retried.
Proof plan — launch the dev app under App Control and capture:
listLanesis the cheapest read)spawnChatwith nolaneIdlanding on a freshly created lane, not the primary worktree — this is the original bug and the one most worth seeinglaneIdreturning the guard error instead of touching primaryAttach via
ade proof capture. Repeat at minimum on Claude and Codex, since those two use completely different registration mechanisms (SDK MCP server vs. dynamic-tool protocol).2.
getCtoAttentionfalse-clears the badge, and its comment says the opposite · Low, but the comment is wrong todayDiagnosis.
apps/desktop/src/main/services/chat/agentChatService.ts:38310. On a throw fromlistIdentitySessions(transient KV/disk error during per-row summarize) the probe returnsidle. The comment at:38320claims "the renderer keeps its last known state rather than falsely clearing a pending question" — but returningidleis a false clear. The comment is actively misleading.Impact is genuinely small: only the badge goes dark, the question is still in the thread, and the probe self-heals on the next cycle (15s visible-tab interval, chat events, focus). A persistent failure means
sessionService.list()is broken and the Work tab is down too.Transport failures do keep the last value (desktop hook and iOS both swallow and retain) — so the two failure modes differ, which is the actual bug. Fixing it properly means an explicit
unknowninCtoAttentionStatethreaded through all three transports (IPC,cto_stateaction,cto.getAttentionsync command) plus iOS.Minimum: correct the comment. Proper: add the third state.
3. Review coverage on both PRs was effectively nil
Diagnosis. #986: Codex and Greptile never triggered; CodeRabbit rate-limited. #987: CodeRabbit landed 2 comments (both already fixed by the
/qualitypass — it independently found the same iOS roster-signature bug) then hit its limit; Codex was out of credits. ~2,400 lines touching all five runtime tool transports merged with only self-review plus the two/qualitytracks.Not a code defect — a coverage fact worth knowing when triaging anything that surfaces in this area. Consider a re-review pass on
774980ff0once bot quota resets.4.
CtoOperatorToolDeps.ptyServicestill restatesPtyCreateArgslossilyapps/desktop/src/main/services/ai/tools/ctoOperatorTools.ts:64. Declarescols?/rows?/title?optional where the realPtyCreateArgsrequires them; the mismatch is silenced by anascast inbuildCtoOperatorToolDeps. #987 fixed the symptom (createTerminalnow passes explicitcols: 100, rows: 30, title) but not the contract — the cast is still load-bearing and will hide the next drift. Fix: importPtyCreateArgsinstead of structurally restating it, then delete the cast.5. CTO context pressure — unmeasured
~80 operator tools are now registered
alwaysLoad: trueon every CTO session, and CTO sessions run withENABLE_TOOL_SEARCH=0(agentChatService.ts, pre-existing). Their schemas now enter context on top of the prompt manifest that already documents them — the manifest is generated from the same tool set, so there is real redundancy. Never measured. Worth a token count on a live CTO session before deciding whether to dropalwaysLoador re-enable tool search.6. Dead code from the
/qualityrestructureensureOrchestrationHttpMcpServerandensureCtoHttpMcpServer(agentChatService.ts:15521,:15525) are thin wrappers with zero remaining callers — everything goes throughensureHttpMcpServer/ensureHttpMcpLeasesdirectly. Delete.7. Pre-existing repo failures (not from these PRs, but real)
Both reproduce on a clean tree and were verified against
origin/main:agentChatService.test.ts > passes discovered local Claude plugins to SDK sessions— picks up/Applications/ADE.app/Contents/Resources/agent-skillsfrom the installed app, so it fails locally for anyone with ADE installed and passes in CI. Should be made environment-independent.syncHostService.test.ts > resubscribes chat listeners after reconnect and routes chat remote commandsagentChatService.tsfrom SDK type drift (SDKControlInterruptResponse.cancelled,RewindFilesResult.skippedLinks)8. Judgment calls — recorded so they are not re-litigated silently
/quality— the fix costs a ref mirror plus a second effect, and the single-writer property forsetDockBadgeCountis worth more than the lag.steerChat,cancelSteer,listSubagents,approveToolUse) are not exposed through the CLI's curated 14-name MCP subset. Product call, consistent with that subset already omitting the git/PR families.main/services/cto/sits at 4 test files against a nominal budget of 3. Forcing 3 means merging two untouched Linear suites (~225 assertions) — unrelated churn with real regression risk.Terminology note for whoever picks this up
The CTO does not have its own lane. Its session is pinned to the project's primary lane (
resolvePrimaryIdentityLane, which throws if no primary exists). Earlier phrasing in this work said "the CTO's own lane", which reads as though it has a private workspace — the opposite of the truth, and the reason the original bug (always default laneId to the CTO's current lane) was so easy to miss. The docs were corrected; if you find that phrasing anywhere, it is stale.Filed from the ADE Work session that shipped #986 and #987.