Skip to content

Fix background job zombie entries and enhance --fresh clearing#435

Open
SparshGarg999 wants to merge 2 commits into
openai:mainfrom
SparshGarg999:fix-zombie-jobs-and-fresh-option
Open

Fix background job zombie entries and enhance --fresh clearing#435
SparshGarg999 wants to merge 2 commits into
openai:mainfrom
SparshGarg999:fix-zombie-jobs-and-fresh-option

Conversation

@SparshGarg999

Copy link
Copy Markdown

This PR fixes background job state machine retaining zombie entries (Issue #410) in openai/codex-plugin-cc:

  1. Auto-Garbage Collection (GC) for Job State (Issue Background job state machine retains zombie entries; --fresh does not clear them #410):

    • Root Cause: Previously completed, failed, or stopped jobs were stored in the state database indefinitely, leading to a build-up of stale/zombie entries.
    • Fix: Implemented an auto-cleanup process in listJobs() (inside state.mjs) that automatically filters out completed, failed, or stopped jobs older than 30 minutes. The associated JSON state files and log files on disk are pruned automatically during this process via the existing saveState logic.
  2. Enhanced --fresh Flag Behavior:

    • Root Cause: The --fresh flag did not actively clean up existing terminal-state jobs, meaning stale jobs still interfered or showed up in /codex:status.
    • Fix: Added a clearTerminalJobs(cwd) function to wipe all terminal-state jobs (completed, failed, or stopped) while keeping active/running jobs. This is invoked in codex-companion.mjs's handleTask when the --fresh option is passed, ensuring a completely clean slate for new runs.
  3. Added Unit Tests:

    • Added unit test cases in tests/state.test.mjs verifying that listJobs correctly prunes expired jobs and clearTerminalJobs removes only terminal jobs.
    • Fixed a test isolation bug in tests/state.test.mjs where multiple test cases resolved to the same temp workspace directory on Windows (due to parent directory traversing for Git repo roots), by calling initGitRepo(workspace) on the unique temp workspaces.

@SparshGarg999 SparshGarg999 requested a review from a team July 5, 2026 03:12

@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: c6397249e7

ℹ️ 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".

Comment thread plugins/codex/scripts/lib/state.mjs Outdated
const state = loadState(cwd);
const now = Date.now();
const nextJobs = state.jobs.filter((job) => {
if (job.status !== "running" && job.updatedAt) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve queued jobs during age-based GC

Queued background jobs are active elsewhere (enqueueBackgroundTask writes status queued, and status/cancel treat queued as active). With this predicate, any queued job whose worker has not transitioned to running within 30 minutes, such as after delayed startup or machine sleep, is removed and saveState deletes its job JSON/log, leaving /codex:status and /codex:cancel unable to see it even though the worker may still run. Restrict GC to explicit terminal statuses instead of every non-running status.

Useful? React with 👍 / 👎.

Comment thread plugins/codex/scripts/lib/state.mjs Outdated

export function clearTerminalJobs(cwd) {
const state = loadState(cwd);
const nextJobs = state.jobs.filter((job) => job.status === "running");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep queued jobs when clearing terminal jobs

This is used by task --fresh, but a background job can still be in the queued state before the detached worker rewrites it as running. Because saveState deletes files for jobs not retained, running a fresh task during that window removes the queued job's request/log and can make the worker fail with “No stored job found” or make the active job impossible to cancel. Keep both queued and running jobs, or filter only known terminal statuses.

Useful? React with 👍 / 👎.

@rajpratham1 rajpratham1 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.

The intent is good, but I don't think this implementation is safe enough yet. The review bot has already pointed out two correctness issues that can cause active jobs to disappear.

Major issues

  1. Queued jobs are incorrectly garbage collected

Current logic:

if (job.status !== "running" && job.updatedAt) {
...
return false;
}

This treats every non-running job as terminal.

But elsewhere the codebase treats queued as an active state.

Possible scenario:

User starts a background task.
Job is stored as queued.
Machine sleeps or startup is delayed.
After 30 minutes, listJobs() runs.
The queued job is deleted.
Worker eventually starts but its metadata/logs are gone.

That is data loss.

GC should only remove known terminal states such as:

completed
failed
cancelled/stopped

not every non-running status.

  1. clearTerminalJobs() also removes queued jobs

Current code:

job.status === "running"

This means --fresh deletes every queued job.

A queued worker is still active.

Removing it can cause:

missing job metadata
failed worker startup
/codex:cancel no longer works
/codex:status can't find the job

Again, terminal states should be filtered explicitly instead of keeping only "running".

Tests miss these cases

The new tests only cover:

running
completed
failed

They should also include a queued job to verify that:

listJobs() preserves queued jobs
clearTerminalJobs() preserves queued jobs

Without those tests, the regression remains undetected.

What is good
Adding automatic cleanup for stale terminal jobs is a reasonable improvement.
Calling clearTerminalJobs() for --fresh makes sense.
Test coverage for completed/failed cleanup is good.

…icit terminal statuses, preserving queued jobs

@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: b6ae0ea4a1

ℹ️ 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".

});
if (nextJobs.length !== state.jobs.length) {
state.jobs = nextJobs;
saveState(cwd, state);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid rewriting job state from stale status reads

When listJobs is called while there is any terminal job older than 30 minutes, this newly-added save writes the entire state object that was loaded before filtering. In a normal background run, a detached worker updates the same state via upsertJob as it moves from queued/running to completed; if /codex:status, /codex:result, or the stop hook calls listJobs between that load and this save, the GC write can overwrite the worker's newer status/thread fields with the stale snapshot, leaving a completed job indexed as running or otherwise losing progress. Re-load/mutate under a lock or remove only expired IDs from the current state.

Useful? React with 👍 / 👎.

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