Repository navigation
Add a bounded persistent diagnostic recorder to the debug flavor #117
Description
Problem
Intermittent Codex Desktop/Gateway disconnects are difficult to reproduce on demand. The current #114 manual harness can capture an isolated bounded run, but it is disposable and previously recorded one event per SSE line; it is not safe or usable as an always-on recorder for normal daily work. Users need a debug flavor that continuously preserves enough privacy-safe pre-failure context to identify which side and phase failed.
Outcome
Add a deep DiagnosticRecorder module at the Gateway transport/lifecycle seam, enabled only by the compile-selected debug flavor from #116. It continuously keeps a bounded rolling two-hour evidence window, automatically or manually freezes an incident snapshot, and exposes status/mark/pause/delete controls without changing request behavior.
The recorder produces the durable evidence consumed by #114 and the versioned incident artifacts exported by #115.
Architecture and interface
Keep the caller interface small:
- record one allow-listed phase transition or aggregate counter;
- mark/freeze an incident with a sanitized reason category;
- query recorder status;
- pause/resume future capture;
- delete frozen incidents.
Hide rotation, coalescing, persistence, recovery, privacy filtering, and retention inside the module. Tests and Gateway callers cross this same seam.
Scope
Continuous bounded capture
- Activate automatically only in a
debugflavor;normalhas no supported runtime path that enables it. - Maintain a continuously rotating last-two-hours window rather than stopping after two hours.
- Enforce a 32 MiB hard storage cap in addition to the time window; evict oldest complete records/segments first.
- Never persist one event per SSE line. Retain first SSE, phase/terminal/close transitions, byte/line totals, and at most 16 periodic aggregate checkpoints per request.
- Correlate requests/connections/retries with opaque process-local labels and monotonic ordering.
- Write through the bounded asynchronous writer from Move Python proxy telemetry writes to a bounded async writer #6 so recorder I/O, rotation, and failures never block or fail a user request.
Evidence schema
Record only allow-listed sanitized fields needed to identify the first-close seam:
- build version/flavor/source revision and schema version;
- route/proxy mode and provider/model provenance already safe for telemetry;
- downstream accept/body/response-open/headers/write/close phases;
- upstream DNS/TCP/TLS/request-write/headers/first-SSE/EOF/read-failure phases where observable;
- relay exposure, protocol terminal observation/forwarding, and local socket closure;
- connection new/reused/unobserved state, retry attempt/budget, request-size bucket, elapsed timings, byte/line counters, and terminal outcome.
Do not retain prompt/response text, reasoning, tool names/arguments/results, request/response bodies, headers or cookies, credentials, environment/configuration values, absolute paths, raw URLs, task/session/thread IDs, or raw exception text that can contain sensitive values.
Incident lifecycle
- Automatically freeze an incident for observable abnormal Gateway terminals, request errors, downstream write failures, and retry-budget exhaustion.
- Provide a visible localized
Mark incidentaction for renderer-only symptoms that the Gateway cannot detect. - Freeze the available pre-incident rolling window plus a bounded five-minute post-marker tail; normal traffic continues during and after freezing.
- Retain at most three frozen incidents and delete incidents older than seven days. Eviction is oldest-first and reported in status.
- Recover or discard incomplete segments atomically after a crash; never present a partial snapshot as complete evidence.
- Expose recorder status: active/paused, flavor, rolling time/bytes, incident count, last marker category/time, truncation/eviction state, and schema version. Do not expose private identifiers.
Integration
- Store recorder state below the authoritative runtime home in a dedicated diagnostics subtree with bounded permissions and atomic metadata updates.
- Define a versioned, allow-listed incident manifest and snapshot layout for Add a privacy-safe diagnostic log export for user support #115 to export without reading internal implementation state.
- Adapt the reusable, sanitized instrumentation/classifier seams proven on Find and fix Responses disconnects across official and external routes #114; do not copy the disposable runner or its unbounded per-line recorder into production.
- Keep provider routing, request/response payloads, retry decisions, terminalization, and Gateway lifecycle unchanged.
Non-goals
- Enabling the recorder in
normalthrough a setting/environment toggle. - Capturing Codex App conversations, renderer contents, raw Codex logs, credentials, or configuration snapshots.
- Automatic network upload or support transmission.
- Fixing Find and fix Responses disconnects across official and external routes #114's initiating disconnect or changing transport/retry/SSE behavior.
- Owning ZIP/save-dialog export UI, which remains Add a privacy-safe diagnostic log export for user support #115.
- Replacing ordinary bounded operational telemetry.
Acceptance criteria
- A packaged debug build starts the recorder automatically and visibly reports it; a packaged normal build has no active recorder or supported activation path.
- The rolling window continuously retains at most the newest two hours and 32 MiB, with deterministic oldest-complete-record eviction.
- A high-volume long stream produces bounded aggregate checkpoints rather than per-line persistence while preserving first-SSE, terminal, totals, and first-close ordering.
- Automatic and manual markers freeze one valid incident with available prehistory and a bounded five-minute tail without interrupting Gateway traffic.
- Retention never exceeds three incidents or seven days; pause/resume/delete/status operations are deterministic and localized.
- Queue/storage exhaustion, permission errors, corruption, crash recovery, and recorder shutdown cannot fail, delay materially, replay, cancel, or alter a model request.
- Privacy fixtures prove forbidden content cannot survive in rolling files, frozen artifacts, status, or manifests; malformed/unrecognized fields fail closed.
- A deterministic fault replay classifies downstream-first, upstream-first, terminal-not-forwarded, and unknown ordering from the frozen artifact without raw content.
- A healthy long stream and normal request behavior are equivalent with recorder enabled/disabled except for bounded diagnostic side effects.
- The versioned incident artifact is consumable through a narrow read-only contract by Add a privacy-safe diagnostic log export for user support #115.
Verification
- Deterministic unit/property tests for time/byte rotation, record-boundary eviction, 16-checkpoint coalescing, three-incident/seven-day retention, clock rollback, crash recovery, and atomic metadata.
- Fault-injection tests for queue overflow, disk full, permission failure, malformed fields, interrupted writes, shutdown, and concurrent markers.
- Privacy scans over rolling/frozen bytes with seeded prompts, tool data, credentials, headers, URLs, paths, IDs, and exception details.
- Find and fix Responses disconnects across official and external routes #114 first-close replay plus a healthy long-stream replay using the versioned frozen artifact.
- Packaged normal/debug Windows smoke proving activation boundary, UI status/mark/pause/delete, continuous Gateway traffic, and no duplicate Gateway.
- Focused/full Python, Rust, frontend/UI-contract gates and
python scripts/report_quality_gates.py(report-only).
Expected hotset
- A new focused Python recorder/writer module and narrow hooks at
src-python/codex_proxy.pytransport/lifecycle seams. - Reusable sanitized instrumentation/classifier code currently proven by Find and fix Responses disconnects across official and external routes #114.
- Runtime-path/config plumbing and focused Tauri commands for status/mark/pause/delete.
- A small debug-only Settings/Diagnostics status surface and localization/UI-contract tests.
- Privacy, rotation, fault, and packaged-smoke fixtures.
Dependencies and relationships
- Blocked by Move Python proxy telemetry writes to a bounded async writer #6 for the reusable bounded async writer.
- Blocked by Build normal and debug flavors from each release version #116 for compile-selected normal/debug flavor identity.
- Blocks Add a privacy-safe diagnostic log export for user support #115's incident-artifact export integration.
- Provides durable evidence to Find and fix Responses disconnects across official and external routes #114; it does not itself close Find and fix Responses disconnects across official and external routes #114.
Execution contract
Execution-Contract: v2
Verification-Class: strict
Verification-Commands:
python -m pytest -qnpm run buildandnpm run test:ui-contractinfrontend/cargo test --lockedandcargo clippy --locked --all-targets -- -D warningsinsrc-tauri/- deterministic recorder privacy/rotation/fault fixtures named in this Issue
Manual-Evidence: packaged normal/debug Windows smoke proving the compile-selected activation boundary, visible recorder controls, continuous Gateway traffic, and no duplicate Gateway
Architecture-Decision: resolved
Review-Owner: orchestrator
Activity
NOirBRight commentedon Jul 14, 2026
First implementation slice after the natural reconnect capture
The natural Debug capture in #114 now validates the need for this recorder's per-attempt timing and connection-disposition fields. It observed two pre-response Official failures (about 61 seconds and 300 seconds), a third attempt that began streaming, and a later downstream close after 381 seconds total; the current generic
tcp_connectlabel cannot distinguish the physical boundary. Evidence: #114 comment 4965232208.To start immediately without colliding with active #106 / PR #122, the first Worker slice is constrained to:
DiagnosticRecorderdeep module;src-python/codex_proxy.pyfor per-attempt start/end elapsed time, response headers, first SSE, terminal/close, retry budget, and opaque connection new/reused/unobserved state;Until #106 integrates, this slice must not modify
src-tauri/src/proxy.rs,src-tauri/tauri.conf.json,src-tauri/src/models.rs, or the official catalog metadata/config hotset. The later Tauri/frontend activation and packaged-smoke slice remains part of #117, but integration is serialized after #106 to avoid duplicate edits and review churn.The Issue remains one strict work item and one future PR. Worker formal Review-Runs must remain 0; the Orchestrator owns the single Standards/Spec review.
NOirBRight commentedon Jul 14, 2026
WORKER_DISPATCH
codex/issue-117-diagnostic-recorder; PR target:dev.DiagnosticRecorder, bounded rolling/privacy/freeze/status core, narrowsrc-python/codex_proxy.pyobservation hooks, and deterministic privacy/rotation/fault/first-close replay tests.Claim and preflight are verified. Scoped edits begin only after the private
CLAIM_CONFIRMEDcontinuation.NOirBRight commentedon Jul 14, 2026
#117 implementation plan — authorized first slice
src-python/diagnostic_recorder.py: a focused, fail-closed recorder over Move Python proxy telemetry writes to a bounded async writer #6's bounded writer with opaque monotonic labels, bounded rolling segments (two hours / 32 MiB), deterministic retention/freeze/status, and read-only artifact/replay helpers.tests/test_diagnostic_recorder.py: deterministic privacy, coalescing (at most 16 checkpoints), rotation/retention, fault isolation, recovery, and sanitized 61s → 300s → streamed → downstream-close replay coverage.src-python/codex_proxy.pyfor upstream attempts/headers, first-SSE/totals/terminal/close, retry budget, and opaque connection disposition. No routing, retry, timeout, transport, payload, or terminalization behavior changes.python -m pytest -qcandidate gate for the changed Python surface;python scripts/report_quality_gates.pyremains report-only; Worker Review-Runs stays 0.tests/test_catalog_sync.py; this slice is a new Python module/test pluscodex_proxy.py, so the path intersection is empty. Tauri/frontend/catalog changes remain excluded.NOirBRight commentedon Jul 14, 2026
Second-slice implementation plan
codex_proxy.main()entry point; the normal flavor keeps the direct child launch.Expected writes:
src-tauri/src/proxy.rs,src-tauri/src/main.rs, new focused diagnostics Rust/Python runtime-control modules and tests, Gateway UI/types/locales, release/packaging checks, and the PR body.src-python/codex_proxy.pywill remain exactly as in the accepted first slice.Collision check: no additional proxy.py edits; no edits to
src-python/model_limits.py,src-python/catalog_sync.py,src-python/config_overlay.py, or #124 context-limit tests. The normal merge brought #106 files forward without overlap.Decision gate: resolved. The debug-only bootstrap will execute the already-owned Gateway entry point in the same child process and use the authoritative diagnostics subtree for bounded control exchange, so activation requires no protected proxy.py change and does not create a second Gateway or port.
NOirBRight commentedon Jul 14, 2026
#117 review-delta fix plan
DiagnosticRecorderfreeze semantics so marker-time prehistory is retained immediately, retention is record-boundary/clock-rollback safe, and incident manifests are strict allow-list readbacks.codex_proxy.pydiagnostic-only changes: observe terminal before downstream write/flush so terminal-not-forwarded is provable, add supported downstream/upstream phase observations and request-size bucketing, and populate connection disposition when safely observable.Decision gate: inspect the existing supported HTTP pool seam for safe new/reused evidence. If it is not observable without a durable architecture expansion, I will send
DISCUSSION_REQUIREDwith the inspected seam before omitting it.NOirBRight commentedon Jul 14, 2026
Packaged Windows smoke feedback — UI delta required
The normal/debug packaged candidate at
c529f6eepassed the runtime portions of the manual gate:Visual acceptance is not complete. The diagnostics panel is too tall and keeps low-frequency details/actions permanently visible, reducing the existing Gateway page's information density.
Accepted narrow UI delta:
aria-expandedstate.The previously passed runtime smoke remains valid. After this UI-only delta, rerun only the packaged debug visual check.
NOirBRight commentedon Jul 14, 2026
UI acceptance delta plan:
frontend/src/components/DebugDiagnosticsPanel.tsx, the English/Chinese locale resources, and the focused UI contract.npm run test:ui-contract,npm run build, diff hygiene, and CI only. The prior full-suite count remains 1.c529f6ee; no Python/Rust/Gateway/build-flavor or Keep Codex auto-compaction within the current Official context budget #124 model/context paths will be touched. The recorded UI contract leaves no material decision gate.NOirBRight commentedon Jul 14, 2026
UI acceptance correction — compact height is part of the contract
The default-collapsed disclosure at
f93f045bhides its detailed content correctly, but the Gateway page still assigns the diagnostics card the flexibleminmax(320px,1fr)row. The result is a large empty card that breaks the page hierarchy.Required final behavior:
minmax(320px,1fr)row;The intended narrow layout correction is a four-row left grid (
auto / auto / auto / minmax(320px,1fr)) with focused UI-contract coverage. This supersedes visual acceptance of the prior collapsed layout; all earlier functional/package evidence remains accepted.NOirBRight commentedon Jul 14, 2026
Implementation plan for the #117 UI allocation delta:
frontend/src/pages/GatewayPage.tsx(one four-row left-grid allocation) andfrontend/scripts/ui-contract.test.mjs(focused regression).auto auto auto minmax(320px,1fr)so status, Recovery, and Debug Diagnostics stay content-sized while only the usage chart receives the flexible row.f93f045b; working tree/index are clean; no Keep Codex auto-compaction within the current Official context budget #124 paths or backend/Gateway recorder surfaces are in scope.npm run test:ui-contract,npm run build,git diff --check, then CI only.NOirBRight commentedon Jul 14, 2026
Final Debug diagnostics information architecture
The inline diagnostics card is rejected even at compact height. Replace it with an on-demand overlay:
build.flavor == debuganddiagnostics_enabled == true; normal builds have neither the trigger nor overlay.This supersedes the prior inline-card visual acceptance. All recorder/Gateway/package evidence remains accepted; only UI/localization/focused UI contracts and visual QA may change.
NOirBRight commentedon Jul 14, 2026
Implementation plan for the final #117 diagnostics UI delta:
frontend/src/pages/GatewayPage.tsx,frontend/src/components/DebugDiagnosticsPanel.tsx(recast as the on-demand overlay), both locale resources, andfrontend/scripts/ui-contract.test.mjs.17d7a355; worktree/index are clean; no Keep Codex auto-compaction within the current Official context budget #124, Python, Rust, Gateway, recorder, flavor, transport, or design-QA artifact path is in scope.npm run test:ui-contract,npm run build,git diff --check, and CI only.NOirBRight commentedon Jul 14, 2026
Completed by PR #125 at merge commit 7287d03. Final CI, Orchestrator review, packaged normal/debug smoke, and maintainer visual acceptance passed. Follow-up notification placement/policy work is tracked separately in #126.