Skip to content

Recover Codex jobs across workspace scopes - #667

Open
villilms wants to merge 6 commits into
openai:mainfrom
villilms:codex/fix-companion-job-recovery
Open

Recover Codex jobs across workspace scopes#667
villilms wants to merge 6 commits into
openai:mainfrom
villilms:codex/fix-companion-job-recovery

Conversation

@villilms

Copy link
Copy Markdown

Summary

  • scope companion commands to CLAUDE_PROJECT_DIR when --cwd is omitted
  • persist background jobs before spawning detached workers
  • recover explicit job IDs across workspace-scoped state directories
  • add regression coverage for cross-workspace status/result and project-directory scoping

Problem

A Claude rescue subagent can launch Codex from a parent/home directory while the target repository is only available through CLAUDE_PROJECT_DIR. The background job then lands in the wrong workspace registry. Later status <job-id> and result <job-id> from the target repository report that the job disappeared even though Codex completed it. The old enqueue order also spawned the worker before its job file existed.

Verification

  • targeted regression suite: 6 passed
  • live Windows smoke test against the installed plugin: background job registered under the target project, recovered from another workspace, completed with CHANNEL_OK
  • full upstream suite: 87 passed; 6 existing Windows-specific failures (Unix path assertion, symlink permissions, and transfer fixtures tied to the real Claude home)

@villilms
villilms requested a review from a team August 22, 2026 10:35

@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: 43e61ea88b

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

status: "queued",
phase: "queued",
pid: child.pid ?? null,
pid: null,

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 the detached worker PID in the queued record

When cancel runs after enqueue returns but before the worker enters runTrackedJob, this null PID makes terminateProcessTree a no-op. The worker does not check whether the stored job was cancelled, so it can subsequently overwrite the cancelled record as running and continue executing, including a --write task. Persist before spawning as intended, but update the queued record with child.pid before returning so cancellation can terminate the worker during this window.

Useful? React with 👍 / 👎.

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

ℹ️ 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 on lines +261 to +267
const crossWorkspace = findCrossWorkspaceJob(reference);
if (!crossWorkspace) {
throw error;
}
return {
workspaceRoot: crossWorkspace.workspaceRoot,
job: enrichJob(crossWorkspace.job, { maxProgressLines: options.maxProgressLines })

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 Recover cross-workspace jobs when cancelling

When this fallback finds an active job from another workspace, the rendered status includes /codex:cancel <id>, but resolveCancelableJob still searches only listJobs(workspaceRoot). Consequently, a background task— including one running with --write—can be monitored from the recovered scope but cannot be stopped there; explicit cancellation should use the same cross-workspace lookup with an active-status predicate.

Useful? React with 👍 / 👎.

@villilms

Copy link
Copy Markdown
Author

Addressed the cancellation race in e06cd2c:

  • added a startup gate so the detached worker cannot enter runTrackedJob before its PID is persisted;
  • the worker checks for a stored cancelled state after the gate opens;
  • added a regression test that cancels immediately after enqueue and verifies the job cannot restart;
  • targeted suite: 7/7 passed.

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: e06cd2c243

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

@villilms

Copy link
Copy Markdown
Author

Addressed the cross-workspace cancellation finding in 93beb53:

  • explicit cancel now falls back to the same cross-workspace lookup used by status/result;
  • fallback is restricted to active queued/running jobs;
  • added a deterministic regression fixture proving cancel from the wrong workspace updates the owning workspace record to cancelled.

Targeted cross-scope cancellation test passed.

@codex review

@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: 93beb533be

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

(job) => job.status === "queued" || job.status === "running"
);
if (crossWorkspace) {
return crossWorkspace;

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 Interrupt the owning workspace's broker

When this fallback resolves a running job from another workspace, handleCancel still calls interruptAppServerTurn(cwd, ...) with the invocation workspace rather than the returned workspaceRoot (codex-companion.mjs:1012). For a job using a workspace-scoped shared broker, that looks up the wrong broker.json and sends the interrupt to a new or unrelated app server; killing the detached worker can then leave the brokered turn—potentially a --write task—running after the command records it as cancelled. Pass the owning workspace to the interrupt call.

Useful? React with 👍 / 👎.

@villilms

Copy link
Copy Markdown
Author

Addressed the owning-broker finding in 7e107ec:

  • handleCancel now calls interruptAppServerTurn with the resolved owning workspaceRoot, not the invocation cwd;
  • added a two-workspace regression test that invokes cancel from the wrong scope and verifies the owning broker receives the expected threadId/turnId;
  • targeted broker-routing test passed.

@codex review

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

ℹ️ 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 on lines +707 to +710
const launchRecord = { ...queuedRecord, pid: child.pid ?? null };
writeJobFile(job.workspaceRoot, job.id, launchRecord);
upsertJob(job.workspaceRoot, launchRecord);
fs.writeFileSync(startGate, "ready\n", "utf8");

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 Recheck cancellation before releasing the worker

When another process cancels the job after the initial queued record is published but before this launch record is saved, handleCancel sees a queued job with a null PID and records it as cancelled, but these lines then overwrite that state back to queued and release the worker. The worker consequently observes the overwritten status and executes the task, including a possible --write task, despite cancellation having succeeded. The new queued-before-spawn ordering is fresh evidence of this distinct race; re-read the stored status before overwriting it or make registration and cancellation atomic.

Useful? React with 👍 / 👎.

@villilms

Copy link
Copy Markdown
Author

Addressed the startup cancellation race in 87db4e2.

The bootstrap job file is now kept out of the shared state index until the detached worker PID is known. The first publicly discoverable queued record therefore always has a usable PID, and the worker gate is released only after both the job file and shared index contain that launch record. There is no longer a visible queued + pid:null record for another process to cancel and have overwritten.

Validation:

  • node --test --test-name-pattern="task --background enqueues|immediately cancelled background task" tests/runtime.test.mjs — 2/2 passed
  • git diff --check — clean apart from the repository's Windows LF/CRLF warning
  • Full npm test was attempted; the existing Windows-only Unix-socket and broken-symlink cases failed, then the suite stalled and was stopped. Neither failure touches this change.

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: 87db4e280c

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

@villilms

Copy link
Copy Markdown
Author

The review requested for current head 87db4e2 is still pending after the initial monitoring window. Re-requesting review for that exact commit. @codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit: 87db4e280c

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

@villilms

Copy link
Copy Markdown
Author

Fixed rescue result delivery in 18cdf7a.

Root cause: the rescue subagent could infer companion task --background for long work. That detached the Codex worker from the subagent, so Claude received only the queued job ID and no completion result; the rescue contract also forbids polling status/result.

The contract now separates the two layers:

  • Claude Code may background the rescue subagent when explicitly requested;
  • the subagent's single companion task invocation must always remain foreground-bound;
  • --background/--wait are stripped before the companion call;
  • final Codex stdout therefore returns through the subagent completion notification.

Validation:

  • node --test tests/commands.test.mjs — 8/8 passed
  • foreground completion runtime tests — 2/2 passed
  • fresh installed Claude session end-to-end smoke test returned exactly HANDOFF_OK
  • local plugin updated and enabled as 1.0.6+local-20260823-072838

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: 18cdf7a57f

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

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.

1 participant