Skip to content

CTO follow-ups: unverified-in-app tool wiring, attention probe false-clear, and residue from #986/#987 #991

Description

@arul28

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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions