fix(server): explain skipped Codex project config#23492

open9
main
fix/issue-15140
opened21d agobyJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 1d ago

Problem

An untrusted Codex project can silently lose its project-local MCP configuration, even in Full Access mode. Sending a catalog-only prompt in a project with a local MCP server returns no tool and gives no actionable explanation.

Change

Read Codex's configuration layers for the current turn directory and display its disabled-project reason through the existing persisted system notices. The optional diagnostic falls back after a read failure or three seconds; configuration bodies are omitted from native protocol logs.

Scope and approval

Fixes #15140

Triaged bug via the automated issue flow. The application displays Codex's explanation without changing project trust.

Verification

  • Regression test failed on main because the assistant answer contained no project-configuration notice.
  • Complete CodexAdapterV2.test.ts: 79 passed after merging main. This includes the disabled-project notice, enabled/non-project controls, timeout continuation, and configuration-log redaction.
  • Targeted lint: 0 errors, two existing unused-declaration warnings. Server tsc-rs --noEmit: exit 0. Formatting hooks and git diff --check passed.
  • Committed reproduction fixture: MCP initialize/list/call, UTF-8 paths, and fixed application clock validated.
  • Real web client: the untrusted project answers “No” and shows the full recovery instruction; the trusted control exposes the probe tool.

Evidence index · Focused verification · Failing regression output

Independent proof audit reproduced the narrow dark capture with zero changed pixels and verified all six image-pair difference maps and counts. Strict matrix coverage remains incomplete for N3 (multiple disabled project reasons); that state is unverified.

Evidence environment

Local web at http://localhost:8103. Native T3 browser, Chromium154 Linux x86_64 desktop UA, DPR2, root font16px, visualViewport.scale1. CSS viewports1440x900 and390x900. Explicit app Light/Dark themes. Whole viewport captured, chat scrolltop0, composer collapsed for narrow stills. Wide sidebar256CSS light,208CSS dark, matched per pair.

Native screenshot export cap1280px is implemented in apps/server/src/preview/ServerBrowserPage.ts (MAX_SCREENSHOT_WIDTH and CDP clip scale). Consequently1440x900CSS/DPR2 stills export1280x800PNG;390x900CSS/DPR2 exports780x1800PNG. Videos retain2880x1800 wide /780x1800 narrow. No images rescaled after capture. This is desktop browser width emulation, not native mobile.

Server, Vite and browser Date fixed2026-10-22T08:05:00.000Z; timers real. Both code versions restored identical pre-turn T3/provider snapshots, same project and thread IDs, exact prompt and local draft. Real provider latency differs; recordings are behavioral comparisons, not frame-aligned pixel diffs. Existing sidebar data copied into isolated home. No live home writes.

Route: /8a17860f-3c27-4a5f-8044-93154022e65d/3e6b8790-416b-4fe8-894c-cb7509147d32 . Title normalized through UI to Project MCP trust. Collapsed warning/answer positions are the intended visible change.

Before and after

Diff0 is amplified binary zero-tolerance proof: white if max absolute RGB channel difference >0; black otherwise. DiffT uses >16/255, also binary. Counts refer to exported PNG pixels. No blanket claim of zero differences outside the change.

RowBefore BASE 3b95e7831bAfter HEAD 91197cb734Diff0 (BASE→HEAD)DiffT16 (BASE→HEAD)
N1 1440 lightbefore1440light.pngafter1440light.png1440light-diff0.png 4264 changed1440light-diffT16.png 3861 changed
N1 1440 darkbefore1440dark.pngafter1440dark.png1440dark-diff0.png 4272 changed1440dark-diffT16.png 3875 changed
N1 390 lightbefore390light.pngafter390light.png390light-diff0.png 6209 changed390light-diffT16.png 5763 changed
N1 390 darkbefore390dark.pngafter390dark.png390dark-diff0.png 6170 changed390dark-diffT16.png 5776 changed
N0 trusted controlbeforeControl.pngafterControl.pngControl-diff0.png 3215 changedControl-diffT16.png 2758 changed
N2 390 dark expandedbefore390dark.pngafterExpanded.pngExpanded-diff0.png 24630 changedExpanded-diffT16.png 22348 changed

What else changed: N1 wide light/dark and narrow dark show the actual warning and displaced No answer/action timestamp. Narrow light additionally has a tiny sidebar-toggle icon raster difference. N2 adds expanded detail and larger answer displacement. Trusted control has Yes in both and no warning; its auto-generated title timing differs (BASE raw prompt title, HEAD Check MCP Tool Availability), plus small submit-button readiness/focus styling. The control is not pixel-identical. Sidebar ages are frozen; no additional age change was identified in the core pairs. Diff masks show all actual differences, without masking or normalizing pixels.

Recordings

No audio track was added. The following written narration accompanies each recording. Times are elapsed media seconds; ranges below are bounded using sampled frames, rather than claiming precise provider event latency.

RowBefore BASE 3b95e7831bAfter HEAD 91197cb734Narration / observed endpoint
T1 arrivalbeforeT1.mp4 25.4032s; start08:18:48.061ZafterT1.mp4 23.283367s; start08:26:02.439Z0s prepared exact prompt; by5s sent. Decoded held-frame PTS: BASE14.989133s still Thinking,16.398167s No (completed by20s), held to end. HEAD4.908533s Thinking without warning;9.517100s warning visible (by10s);14.442333s No, one warning held to end. Provider timing differs. These bounds supersede the earlier incorrect BASE-by15s and HEAD-warning-by5s narration.
T2/T5 detail/reloadbeforeT25.mp4 5.969367s; start08:22:16.978ZafterT25-final.mp4 64.956467s; start08:39:26.003ZBASE has no warning to expand; reload preserves absence/No. HEAD exact verified timeline below.
T3 trust next turnsbeforeT3.mp4 40.478933s; start08:22:58.287ZafterT3.mp4 35.3048s; start08:29:04.111ZBoth begin from untrusted first-turn history. Trusted helper transition occurs outside public browser view at start; next same-thread catalog answer remains No (native session catalog caching), with no NEW warning. BASE trusted answer complete by10s; reverse untrusted transition at29s, next turn Thinking at35–40.48s endpoint. HEAD trusted answer complete by15s; reverse at26s; new warning visible by30s, final answer still Working/streaming at35.30s endpoint. Neither clip proves final reverse-turn completion. Fresh trusted N0 proves Yes availability.

T2/T5 final HEAD media narration uses decoded frame PTS (sparse variable-frame-rate video, ~1fps average). Seeking with ffmpeg -ss can return the NEXT frame, so requested seek time is not a measured frame timestamp. Auditor decoded every frame to establish these bounds:

Media time / boundNarrated observation
0sStarts expanded from preceding inspection.
By11s, still collapsed at19sVisible row header clicked to establish collapsed state; tool independently verified aria-expanded=false.
By22sExpanded full reason, readable project and provider-home trust instruction.
By32.974700sCollapsed again; verified aria-expanded=false.
32.974700–40.275133sReload transition is bounded by the last collapsed frame and first blank-loading frame.
40.275133s /40.359sBlank loading, then splash.
45.684267s /48.725667sSplash, then partial page.
49.947667sRestored page, persisted notice collapsed.
~50.62s; settled51.02sExplicit postreload expansion; aria-expanded=true, height150.48CSS verified.
51.02–64.92sComplete recovery reason held visible through final decoded frame. Video duration64.956467s.

Separate wall-clock action log (UTC, not inferred media PTS): recording start08:39:26.003; header collapse08:39:35.975; expand08:39:46.478; collapse08:39:58.515; reload08:40:05.858; postreload expand08:40:16.613; stop08:40:30.912. Tool call times and sparse media frames have different precision. Neither is relabelled as the other.

The earlier afterT25.mp4 and afterT25-corrected.mp4 are superseded; their collapse/final expansion sequence was insufficient. Clicking the inspector center can hit detail instead of the header; final recording uses the actual visible header and explicit state checks.

Safe trust transitions changed only disposable config.toml. No credentials or editor/terminal config content appears in recordings. Trust was restored UNTRUSTED; root then restored first-turn checkpoint for independent audit. No provider turns were issued after that restore.

Evidence limits

Captured required: N1 all4 width/theme pairs; N0 trusted pair; N2 expanded pair; T1 arrival pair; T2/T5 detail/reload pair; T3 trust-transition pair with explicit endpoint limitation. Missing required conditional row: N3 stacked notices UNCOVERED. Real fixture exposes one disabled project layer; no real nested layer or labelled protocol harness capture was produced. Source review is not substituted for this row. Additional unverified: N4 whitespace harness, deterministic T4 failures/races, T6 goal continuation, remote/relay/tunnel runtime, native Windows desktop, native iOS/Android expansion/copy. Narrow web is not native proof. T3 final untrusted turn completion is not captured. It is not claimed by the stills or descriptions. No duplicate provider tests/checks run by capture worker. Parent separately reported79 adapter tests, targeted lint and server typecheck passing.

Unverified: Windows desktop (cannot-verify desktop) and native mobile runtime, plus the fixture limits above.

Model: GPT-6.1-Sol. Harness: Codex in T3 Code.

Fixes pingdotgg/t3code#15140

Checks

22 successful · 12 skipped
All checks passed
4b2c6cf

Added 7 commits

  • Merge origin/main into fix/issue-151401d29329
  • test(server): support Unicode paths in Codex trust fixture314df4d
  • fix(server): explain skipped Codex project config4e84826
  • test(server): add Codex project trust reproduction fixture551e8c3
  • test(server): align Codex replay fixture prompts6bb6733
  • test(server): freeze Codex trust capture clock91197cb
  • test(server): reproduce missing project MCP warning for #15140e2ec9ac
Synced from GitHub

Approvability

Verdict: Approved at 4b2c6cf

Macroscope's review found this PR approvable — Adds notices explaining why Codex skipped project configuration, without changing trust or configuration behavior. The diagnostic is time-bounded and tolerates unsupported servers, while config bodies are redacted from protocol logs.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire5.0 KiB5.0 KiB0 B (0.0%)6.8 KiB✅
CodexThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
CodexLive turn WebSocket wire1.2 KiB1.2 KiB0 B (0.0%)2.0 KiB✅
CodexLive turn WebSocket decoded21.5 KiB21.5 KiB0 B (0.0%)24.4 KiB✅
CodexLive turn messages110 (0.0%)8✅
ClaudeTotal thread wire5.0 KiB5.0 KiB0 B (0.0%)6.8 KiB✅
ClaudeThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
ClaudeLive turn WebSocket wire1.2 KiB1.2 KiB0 B (0.0%)2.0 KiB✅
ClaudeLive turn WebSocket decoded21.9 KiB21.9 KiB0 B (0.0%)24.4 KiB✅
ClaudeLive turn messages110 (0.0%)8✅

Baseline: 3a8c250 · PR result: 4b2c6cf · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Synced from GitHub

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

The adapter now reads Codex configuration layers before starting a turn and emits system notices for disabled project layers. Protocol logs redact values under the config key. Tests cover configuration-read errors and timeouts, notice emission, and log redaction.

Changes: Project Configuration Notices

Layer / File(s)Summary
Read project configuration
apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts, apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts, apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.testkit.ts
Before a native turn starts, the adapter reads configuration layers for the working directory with a three-second timeout and retains disabled project-layer reasons. Replay support handles configured responses and method-not-found errors. Tests cover a read that does not complete.
Emit project-layer notices
apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts, apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts
Root-turn registration emits completed system notice items for retained project-layer reasons. A test checks the notice and assistant message.

Protocol Log Redaction

Layer / File(s)Summary
Redact configuration values in protocol logs
apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts, apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts
Protocol-value redaction replaces values under the config key. A test checks that top-level and project-layer database URLs do not appear in logged payloads.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

mermaid
1sequenceDiagram
2 participant CodexAppServer
3 participant CodexAdapterV2
4 participant RootTurnRegistration
5 CodexAppServer->>CodexAdapterV2: Return config/read layers
6 CodexAdapterV2->>CodexAdapterV2: Retain disabled project-layer reasons
7 CodexAdapterV2->>RootTurnRegistration: Provide retained reasons
8 RootTurnRegistration->>RootTurnRegistration: Emit completed system_notice items

Suggested reviewers: t3dotgg

Merge Risk: External image Minimal · up to 4b2c6

No identified issue blocks merging the project-configuration notices and log redaction after normal checks.

Pre-merge checks: Passed 4

Check nameStatusExplanation
Title checkPassedThe title is concise, uses conventional commit format, and clearly identifies the main change: explaining skipped Codex project configuration.
Description checkPassedThe description includes all required sections. It explains the problem, implementation, scope and issue link, and detailed verification results. It also identifies unverified scenarios and limitation…
Linked Issues checkPassedFor [#15140], startTurn requests config/read with the current turn cwd and includeLayers: true. It collects disabledReason from disabled project layers and emits each reason as a persisted `…
Out of Scope Changes checkPassedThe whole-PR changes remain connected to [#15140]. Adapter changes add the configuration diagnostic, timeout handling, persisted notice emission, and native protocol redaction. Test and replay-harness…
Full details: Description check

Explanation

The description includes all required sections. It explains the problem, implementation, scope and issue link, and detailed verification results. It also identifies unverified scenarios and limitations.

Full details: Linked Issues check

Explanation

For [#15140], startTurn requests config/read with the current turn cwd and includeLayers: true. It collects disabledReason from disabled project layers and emits each reason as a persisted system_notice. The diagnostic uses a three-second timeout and an empty fallback for request failure, so the turn can continue. Protocol logging redacts complete config values. Tests cover the project notice, control cases, timeout continuation, and configuration redaction. The proposed trust action and scoped refresh are not required because [#15140] identifies the missing diagnostic as the reported failure.

Full details: Out of Scope Changes check

Explanation

The whole-PR changes remain connected to [#15140]. Adapter changes add the configuration diagnostic, timeout handling, persisted notice emission, and native protocol redaction. Test and replay-harness changes validate those behaviors and support older app-server responses. The changes do not modify project trust or add unrelated product behavior.

Finishing Touches

Generate docstrings

  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)

  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Usage-based review receipt

Note

This review was completed with usage-based billing: files reviewed beyond your plan's included limits are billed at $0.25/file. View usage-based billing.

External image Recent review info

External image Run configuration

External image Commits

Reviewing files that changed from the base of the PR and between 91197cb734f1b7cfc1ec440591eed38ff6e56dff and 4b2c6cf9ef2e202c2f850267ba057cb5e5d19696.

External image Files selected for processing: 3

  • apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts
  • apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.testkit.ts
  • apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts

Limit details: You’ve used all 10 included reviews currently available.

Added 2 commits

  • Merge remote-tracking branch 'origin/main' into fix/issue-15140389419e
  • test(server): tighten Codex project config coverage after merge4b2c6cf

Evidence

Verified with real Codex turns in the web client, Full Access, Chromium 1280×800, light theme, and fresh isolated databases on both commits.

  1. Configure a working project-local MCP server and an untrusted Codex home.
  2. Start a fresh chat asking whether its tool is available, without changing trust.
  3. Inspect the notice, reload, and expand the persisted recovery instruction.

Both agents reported the tool absent. Base provided no trust explanation; head displayed Codex’s instruction naming the affected project and provider configuration file.

Synthetic seed · Reproduction driver and browser steps

AI-assisted; verified with the tests and tools listed above.

Not shown:

  • Desktop and mobile were not exercised; this proof covers the real web client and server.
  • Older or unresponsive Codex config/read fallback and protocol-log redaction were not demonstrated by this reproduction.
  • The scenario runner was not used because its generic replay provider cannot reproduce Codex-specific project-trust behavior.