How to Debug When Job Status Shows Incorrect Information for Codex
When the codex status command displays stale or wrong data, the issue usually stems from persistence failures, async race conditions, or corrupted state files in the companion process.
The openai/codex-plugin-cc repository implements a job-tracking system that maintains an in-memory state object and persists it to disk so the companion process survives restarts. When you need to debug incorrect job status information, you must trace the flow from the API wrapper through the lifecycle controller to the underlying JSON store.
Understanding the Job Status Architecture
State Management in state.mjs
The global jobsById Map lives in plugins/codex/scripts/lib/state.mjs. This module exposes the helpers getJob, setJob, and updateJobStatus, which mutate the in-memory state. Every mutation must be followed by saveState() to serialize the data to .codex/state.json.
Job Tracking in tracked-jobs.mjs
The TrackedJob class in plugins/codex/scripts/lib/tracked-jobs.mjs wraps raw jobs with timestamps and progress callbacks. Its finalize() method writes the definitive status—pending, running, failed, or succeeded—back to the state store.
Lifecycle Orchestration in job-control.mjs
plugins/codex/scripts/lib/job-control.mjs contains the central entry points startJob, cancelJob, and completeJob. According to the source code, these functions are the only places that should modify the status field.
API Integration in codex.mjs
plugins/codex/scripts/lib/codex.mjs provides the thin wrapper around the actual Codex API. It reports success or failure back to the job controller, triggering the status update chain.
Root Causes of Incorrect Status Reporting
- State persistence failure: The companion process crashed before
saveState()flushed the in-memory map to disk. - Async race conditions: The UI reads the state after the job finishes but before
updateJobStatusresolves. - Corrupted JSON storage: Malformed data in
.codex/state.jsoncauses the loader to fall back to an empty state. - Job ID collision: Manual assignment of job IDs outside the
TrackedJobconstructor can overwrite existing entries.
Systematic Debugging Steps
-
Verify the job exists in the current state:
codex status --id <job-id>If the ID is missing, the companion never persisted it.
-
Inspect the raw state file:
cat .codex/state.json | jq .Check the
statusfield for your target job ID. If the file is empty or the entry is absent, the write operation failed. -
Enable verbose companion logging:
CODEX_DEBUG=1 codex companionSearch the logs for lines containing your job ID and the phrase "updating status" to confirm the lifecycle fired.
-
Reproduce with a minimal job:
codex run --prompt "print('hello')" --output json codex status --id <new-job-id>If the status is correct for new jobs, the issue is likely data corruption from previous runs.
-
Validate the JSON schema: Ensure
.codex/state.jsonconforms to the schema defined inplugins/codex/schemas/review-output.schema.json. Invalid JSON will cause the parser to drop the entire state. -
Trace the async lifecycle hooks: Add temporary
console.debugstatements insidesetJobandupdateJobStatusinstate.mjs. Restart the companion and watch the logs; the "running → succeeded" sequence should appear without gaps. -
Check for duplicate job IDs: Search the codebase for manual
jobId:assignments outside ofTrackedJobcreation. Duplicate IDs cause one job to overwrite another's status. -
Capture unhandled rejections: Run the companion with
node --trace-warningsto surface promise rejections incodex.mjsthat abort the update flow, leaving the status stuck at "running".
Common Fixes and Solutions
Status stuck on "running" after completion
- Root Cause: The companion process exited before
saveState()flushed the final state to disk. - Fix: Ensure the companion stays alive until
await runJob()resolves, or add aprocess.on('exit')guard that callssaveState()synchronously.
Stale data after companion restart
- Root Cause: Corrupted or missing
.codex/state.jsoncauses the loader to initialize an empty state. - Fix: Add defensive JSON parsing in
state.mjsthat falls back to an empty Map ifJSON.parsethrows.
Wrong job ID displayed in status
- Root Cause: The client passed an incorrect
--idflag, or a manual job ID collided with an existing entry. - Fix: Validate the provided ID against
state.jobsByIdkeys injob-control.mjsbefore querying, and emit a clear "Job not found" error.
Inconsistent status between CLI and UI
- Root Cause: A race condition where the status is read between job completion and the
updateJobStatuspromise resolution. - Fix: Serialize status updates using an async lock in
job-control.mjsto prevent interleaved reads and writes.
Summary
- The state object in
plugins/codex/scripts/lib/state.mjsis the source of truth, persisted to.codex/state.json. - Incorrect job status usually indicates a failure in
saveState(), async timing issues, or ID collisions intracked-jobs.mjs. - Debug by inspecting the raw JSON, enabling
CODEX_DEBUG=1logs, and validating againstreview-output.schema.json. - Ensure
TrackedJob.finalize()and thejob-control.mjslifecycle methods are always invoked to prevent stuck states.
Frequently Asked Questions
Why does my job status remain "running" after the task completed?
This happens when the companion process exits before saveState() persists the final state. Ensure the process stays alive long enough for the completeJob promise to resolve, or add a graceful shutdown handler in job-control.mjs that flushes pending writes.
Where is the job state actually stored?
The in-memory state lives in the jobsById Map inside plugins/codex/scripts/lib/state.mjs, which serializes to .codex/state.json on disk. If this file is corrupted or deleted, the status command defined in plugins/codex/commands/status.md will report empty or stale data.
How can I verify the state file is not corrupted?
Validate .codex/state.json against the JSON schema defined in plugins/codex/schemas/review-output.schema.json using ajv-cli or a Node script. If parsing fails, state.mjs should fallback to an empty Map to prevent crashes.
What causes race conditions in job status updates?
Race conditions occur when codex status reads the state between the job completion event in codex.mjs and the updateJobStatus promise resolution. To fix this, serialize updates using an async lock in plugins/codex/scripts/lib/job-control.mjs so reads cannot interleave with writes.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →