Skip to content

feat(cli): show patch tasks in Codex desktop - #477

Open
ianw-oai wants to merge 1 commit into
mainfrom
dev/ianw/show-patch-tasks-in-codex
Open

feat(cli): show patch tasks in Codex desktop#477
ianw-oai wants to merge 1 commit into
mainfrom
dev/ianw/show-patch-tasks-in-codex

Conversation

@ianw-oai

Copy link
Copy Markdown
Collaborator

Summary

Patch tasks did not appear in Codex desktop because the CLI created ephemeral codex exec sessions. The desktop app also excludes saved exec sessions from its task list.

Changes

  • Run patching through the bundled Codex app server and create a saved desktop-visible task.
  • Preserve the validation command, workspace-write sandbox, approval policy, disabled plugins, final-response output, and redacted failures.
  • Cover app-server patch completion and authentication failures, and document saved patch tasks.

Testing

  • bun test tests-ts/cli-skills.test.ts tests-ts/cli.test.ts tests-ts/cli-authentication.test.ts tests-ts/cli-signals.test.ts --randomize --seed 12345 --timeout 30000: 183 passed.
  • node scripts/generate-models.cjs --check.
  • tsc --noEmit.
  • tsc -p tsconfig.build.json.
  • prettier --check --ignore-path .gitignore --ignore-path .prettierignore '**/*.{cjs,mjs,js,ts,json,md}'.
  • Ran the built CLI against a local synthetic Responses server and verified the saved task appeared in the default app-server thread list.

Risk and rollout

Patch sessions are now saved in the configured Codex home and can contain finding details. The app server reads user configuration, but plugins remain disabled and the workspace-write sandbox and never approval policy stay enforced. The CLI uses its bundled Codex executable; the desktop app is optional.

Public disclosure review

  • No customer, partner, prospect, or user identities, data, or identifying details are included.
  • No credentials, personal data, private source, scan findings, or nonpublic links or tickets are included.
  • I reviewed the branch name, title, description, commits, changes, comments, logs, screenshots, attachments, and links for public disclosure.

@github-actions github-actions Bot added the enhancement New feature or request label Aug 16, 2026
@ianw-oai
ianw-oai marked this pull request as ready for review August 16, 2026 04:49

@mldangelo-oai mldangelo-oai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

looks great!

@mldangelo-oai mldangelo-oai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Non-blocking follow-up to my earlier comment on 43fd1b26. The installed package's normal patch and resume path worked. The additional protocol tests found three cases worth covering: a child task can finish the parent command, a server request for user input can go unanswered, and an early disconnect can return success.

The full local suite passed 1,288 tests with 11 skipped, and the current GitHub checks are green. I left the observed behavior and suggested regression tests inline. A small app-server helper shared with #471 would give these cases one place to live.

Comment thread sdk/typescript/src/cli.ts
Comment on lines +3319 to +3322
if (turn.status !== "completed") {
error = turn.error?.message ?? "Codex did not complete the patch.";
}
appServer.input.end();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Non-blocking: this notification can belong to a child task. In the installed CLI test, a child's item/completed and turn/completed produced exit 0 with the child's answer, then closed stdin before the parent finished. Could we retain the thread ID and turn ID returned by the start requests and accept messages and completion only for that pair? A child-finishes-first regression should still return the parent's final answer.

Comment thread sdk/typescript/src/cli.ts
Comment on lines +3283 to +3287
if (appServer !== undefined && value["id"] !== undefined) {
const responseError = value["error"] as { message: string } | undefined;
if (responseError !== undefined) {
error = responseError.message;
appServer.input.end();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Non-blocking: server-initiated JSON-RPC requests also have an id. A normal item/tool/requestUserInput request received no reply in QA, so the command waited until it was terminated. I'd route method-bearing requests separately from responses to our pending requests. Unsupported interactions should receive an explicit response or end with a clear saved-task handoff, while preserving the configured approval policy. An overlapping client/server request ID is worth covering too.

Comment thread sdk/typescript/src/cli.ts
Comment on lines +957 to +960
let [status, events] = await Promise.all([invocationStatus, captured]);
if (status === 0 && output?.appServer !== undefined && events?.error) {
status = 1;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Non-blocking: a clean app-server exit does not tell us whether the patch turn finished. A commentary message followed by EOF returned exit 0 and printed the commentary as the result. Could the reader return an explicit completion state and the matching final-answer item, and have this path check both? The early-EOF case should report an incomplete result even if the process exits cleanly.

Comment thread sdk/typescript/src/cli.ts
Comment on lines +3249 to 3254
appServer?: {
readonly directory: string;
readonly prompt: string;
readonly input: NodeJS.WritableStream;
},
): Promise<{ message?: string; error?: string; malformed: boolean }> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Non-blocking simplification: I'd keep the legacy codex exec decoder separate and move the app-server lifecycle into a small typed helper shared with #471. It could own request IDs, routing, the active thread and turn, completion, redacted errors, and shutdown. That would remove the mixed protocol state machine from this output parser and give the cases above one place to be tested.

Comment thread sdk/typescript/src/cli.ts
Comment on lines +3300 to +3305
const { thread } = value["result"] as { thread: { id: string } };
send({
id: 3,
method: "turn/start",
params: {
threadId: thread.id,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Non-blocking UX suggestion: could we return this task ID and give the task a useful name? The task was resumable in QA, but it had no name, its preview started with the implementation prompt, and the CLI did not identify which task to open. A short title and a supported open/resume instruction on stderr would make the handoff easier without changing final-response stdout. The common structured result could carry the ID too.

Copy link
Copy Markdown
Collaborator

Non-blocking design summary for #475, #477, #471, and #461.

I think the split is right: keep the pinned scan runtime isolated, share task visibility, and let the user continue a saved patch task. I'd keep the implementation small:

Across the four PR heads and a #475+#477 integration tree, five full local suite runs produced 6,336 passes and 55 skips. The real-model patch and saved-task resume paths worked with synthetic fixtures. The current heads do not yet provide one complete Linear issue to GitHub PR workflow; I did not use live Linear or create an external PR.

My suggested follow-up order is the #477 lifecycle tests, the #471 naming and Windows cleanup, then the common patch result and publication recovery. After those pieces are joined, one explicitly scoped live acceptance test would close the remaining gap.

Detailed notes: #475, #477, #471, #461.

@zcrab-oai zcrab-oai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Found a project-isolation regression in the transition from ephemeral patch execution to persistent desktop tasks.

Comment thread sdk/typescript/src/cli.ts
[
"exec",
"--ignore-user-config",
...(patch ? ["app-server"] : ["exec", "--ignore-user-config"]),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P1] Preserve project isolation for desktop patch tasks

Could saved patch tasks retain the previous isolated-configuration behavior without silently changing persistent project trust? Starting a workspace-write app-server thread can load user/project configuration and activate standalone MCP servers even when plugins are disabled. Please ensure repository trust is not automatically promoted and project-configured MCP servers remain inactive before the task starts, with an integration test covering an initially untrusted project.

@kmbroai kmbroai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Preapproving - should fix some of the comments

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants