Skip to content

fix(advisor): restore advisor consults for claude and codex - #377

Merged
heath-s merged 2 commits into
mainfrom
fix/advisor-not-working
Aug 14, 2026
Merged

fix(advisor): restore advisor consults for claude and codex#377
heath-s merged 2 commits into
mainfrom
fix/advisor-not-working

Conversation

@heath-s

@heath-s heath-s commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

Advisor consults never completed. Three independent defects sat in the path — one broke every consult, one broke Codex isolated threads, and one crashed the host service whenever Provider Debug Logging was on. All three are fixed and verified against a running desktop build.

Changes

Fixes

  • advisor: forward stave_consult_advisor to the host-service process instead of resolving grants in Electron main. Grants are issued in the child process, so every consult previously failed with unknown-consult-key. The consult now travels over provider.consult-advisor, the same seam provider.skip-advisor and respond-approval already use.
  • advisor: raise the provider.consult-advisor request backstop to 15 minutes. It previously equalled the runtime's own 10-minute ceiling for the highest effort tiers, so the backstop could fire first and replace the runtime's advisor-timeout result with a transport error the primary cannot read.
  • provider: stop quoting Codex config override keys. Codex splits an override key on . and takes each segment verbatim without parsing TOML quoting, so mcp_servers."name".enabled registered a new server whose name contained quote characters and had no transport — failing config load for every isolated thread (Advisor and secondary review). MCP disables now go through a nested value, which keeps names containing dots exact.
  • provider: remove plugin-runtime-injected MCP servers with features.apps=false rather than by name. codex_apps has no mcp_servers entry to disable, so naming it created a transport-less table and reproduced the same load failure. Isolation is genuinely stronger: read_mcp_resource, list_mcp_resources, and request_plugin_install disappear from isolated threads.
  • provider: fix the same quoting defect in bundled-plugin disables. plugins."chrome@openai-bundled".enabled=false created a decoy entry and left the real plugin enabled — every Codex turn had been sending a disable that did nothing.
  • host-service: guard stdout. Its stdout is the protocol frame channel, and console.debug writes there, so enabling Provider Debug Logging produced invalid message frame header and collapsed the host service. A stdout guard now claims the channel, leaving the real writer to the frame layer and redirecting all other stdout writes to stderr.

Test Evidence

bun run typecheck — clean.

Focused suites — 147 pass / 0 fail across the 10 advisor and Codex test files. 22 new regression tests, each confirmed Red before the fix:

  • tests/advisor-consult-process-boundary.test.ts — main must not import providers/advisor-consult; the host service must dispatch it; the backstop must exceed the longest advisor timeout across every effort tier.
  • tests/codex-config-override-keys.test.ts — pins "no quote characters in override keys" as an invariant.
  • tests/codex-isolation-config-overrides.test.ts, tests/host-service-stdout-guard.test.ts, tests/provider-runtime-advisor.test.ts, tests/local-mcp-service-bridge.test.ts.

Full suite: 3745 pass / 12 fail. The 12 failures match the pre-existing baseline on main and all pass when run in isolation — parallel-execution contamination, untouched by this branch.

Manual verification in an isolated desktop build (separate --user-data-dir and HOME, sandbox .codex/config.toml restored byte-for-byte from the real one):

Case Result
Codex advisor · gpt-5.6-sol · xhigh ok:true · 7.8s
Codex advisor · gpt-5.6-terra · xhigh ok:true · 8.2s
Advisor with Provider Debug Logging on ok:true · 6.1s · 0 frame-header errors, 19 debug lines on stderr
Claude advisor · Fable 5 advice returned, turn completed normally
Codex primary turn (regression) passes

Before/after on the same instance and same task: the prior build returned {"ok":false,"code":"advisor-failed","message":"failed to load configuration: invalid transport in mcp_servers.\"codex_apps\""}; the fixed build returned {"ok":true,...}. ~/.codex/config.toml and ~/.stave/local-mcp.json checksums were identical before and after, confirming the plugin disables do not leak into user global config.

Notes

  • bun run typecheck does not cover electron/tsconfig.json's include is src only. The electron/ changes were verified separately under a temporary config; per-file error counts match the baseline exactly (208 pre-existing, 0 added). The coverage gap is a repository-level issue and is left out of scope here.
  • Unrelated, found while verifying and not fixed here: electron/main/runtime-profile.ts computes a -dev userData path and then discards it, so dev:desktop runs against production userData with no single-instance lock. Manual verification had to launch build output with explicit isolation flags to work around it.

Advisor consults never completed. Three independent defects were in the
path, each verified against a running desktop build.

1. `stave_consult_advisor` resolved grants in the Electron main process,
   but grants are issued in the host-service child process, so every
   consult failed with `unknown-consult-key`. The consult now forwards
   over `provider.consult-advisor`, the same seam already used by
   `provider.skip-advisor` and `respond-approval`. The host-service
   request backstop is raised to 15 minutes so it cannot pre-empt the
   runtime's own 10-minute `advisor-timeout` outcome with a transport
   error the primary cannot read.

2. Codex config override keys were quoted. Codex splits an override key
   on `.` and takes each segment verbatim without parsing TOML quoting,
   so `mcp_servers."name".enabled` registered a new server whose name
   contained quote characters and had no transport, failing config load
   for every isolated thread. Keys are now bare, MCP disables move to a
   nested value so names with dots stay exact, and servers injected by
   the plugin runtime are removed with `features.apps=false` rather than
   named. The same quoting bug silently no-opted every bundled-plugin
   disable; those are corrected too.

3. Provider debug logging killed the host service. Its stdout is the
   protocol frame channel and `console.debug` writes there, producing
   `invalid message frame header`. A stdout guard now claims the channel
   so only the frame layer holds the real writer and all other stdout
   writes are redirected to stderr.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@heath-s
heath-s requested a review from a team as a code owner August 14, 2026 02:55
@heath-s
heath-s requested review from dodonmountain and paul-seo73 and removed request for a team August 14, 2026 02:55

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0229b22cd0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return {
"features.apps": false,
...buildCodexMcpDisableConfigOverrides(
reachable.filter((name) => configured.has(name)),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Disable all cwd-specific configured MCP servers

When an isolated Codex turn runs in a project with a project-scoped MCP server, this intersection can omit it: mcpServerStatus/list is queried without a cwd on the shared App Server spawned at process.cwd(), while config/read and the eventual thread use args.cwd. A server present only in the target project's configuration therefore appears in configured but not reachable, remains enabled at thread/start, and can give Advisor or secondary read-only turns network or mutation tools despite the isolation guarantee. Build disables for every server returned by the cwd-specific configuration, using the catalog only to handle injected servers that lack configuration entries.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in b588ccf.

Reproduced against codex-cli 0.146.0 with an App Server started in a neutral directory and projsrv declared only in the project layer:

catalog_no_cwd    : ["globalsrv"]            # mcpServerStatus/list
catalog_with_cwd  : ["globalsrv"]            # cwd param is ignored
config_with_cwd   : ["globalsrv","projsrv"]  # config/read {cwd}

And confirmed the leak is real rather than masked by override replacement — nested mcp_servers overrides merge, so projsrv survived into the isolated thread:

{mcp_servers:{globalsrv:{enabled:false}}}                   -> thread/start OK, projsrv still live
{mcp_servers:{globalsrv:{...}, projsrv:{enabled:false}}}    -> thread/start OK
{mcp_servers:{globalsrv:{...}, codex_apps:{enabled:false}}} -> FAILED: invalid transport in `mcp_servers.codex_apps`

The third case is why the intersection existed, but it points at the right source rather than at the catalog: config/read {cwd} is already exactly the set of names that have an mcp_servers entry, so it is both complete and safe to name. Injected servers such as codex_apps stay covered by features.apps = false. The mcpServerStatus/list request is now dropped from this path entirely.

Regression coverage added at both levels — resolveCodexIsolationConfigOverrides and the thread/start params built by runCodexReadOnlyPromptWithClient — each verified red before the fix.

@heath-s
heath-s enabled auto-merge (squash) August 14, 2026 03:03
@heath-s
heath-s requested a review from astyfx August 14, 2026 03:03
@heath-s
heath-s disabled auto-merge August 14, 2026 03:03
`resolveCodexIsolationConfigOverrides` gated the disable set on the
`mcpServerStatus/list` catalog. That catalog belongs to the shared App
Server process and accepts no cwd, so a server declared in a project
config layer between the thread's cwd and its repo root never appears in
it. Intersecting it with `config/read {cwd}` dropped exactly those
servers from the override, leaving them reachable inside a thread that
advertises isolation.

Source the names from `config/read {cwd}` alone. It resolves the project
layers, and it is also precisely the set of names that have an
`mcp_servers` entry — which is what makes them safe to name at all.
Codex-injected servers such as `codex_apps` still have no entry and are
still neutralized by `features.apps = false`, so nothing gains a
transport-less table. The catalog request is dropped entirely.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@heath-s
heath-s merged commit ab9f0e1 into main Aug 14, 2026
4 checks passed
@heath-s
heath-s deleted the fix/advisor-not-working branch August 14, 2026 03:20
@astyfx astyfx mentioned this pull request Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants