Skip to content

fix(acp): recover when compact is interrupted - #275

Open
UniversePeak wants to merge 4 commits into
LodyAI:mainfrom
UniversePeak:fix/stop-compaction
Open

fix(acp): recover when compact is interrupted#275
UniversePeak wants to merge 4 commits into
LodyAI:mainfrom
UniversePeak:fix/stop-compaction

Conversation

@UniversePeak

Copy link
Copy Markdown

Related issue

Closes #267

Problem / pressure

Stopping /compact while Codex is showing the context-compaction activity leaves the ACP prompt waiting for thread/compacted. Because compaction is a non-turn command, the existing cancellation path cannot find a turn to interrupt, so the prompt remains active and later prompts fail with A Codex prompt is already active.

Summary

  • Track the non-turn compaction lifecycle on the owning Codex ACP prompt.
  • Route ACP cancellation directly to that prompt while compaction is in flight, allowing the prompt to return cancelled and release its active-prompt guard.
  • Keep the session stop action available while the compaction activity is visible.
  • Add a regression test that cancels /compact before the provider's terminal compaction notification arrives and verifies a late notification is harmless.

The ACP adapter portion is contributed in the companion change LodyAI/acp-extension-codex#30, and this PR updates the repository submodule pointer to that tested revision.

Before / after

Before After
Cancelling /compact has no turn id to interrupt, so the ACP prompt stays active and future prompts are rejected. Cancellation aborts the owning non-turn compaction prompt, and the session can accept a subsequent prompt after the cancelled operation settles.
The stop control depends only on ordinary active-turn status. The stop control remains available while context compaction is reported as active.

Test plan

  • corepack pnpm --filter acp-extension-core build — passed.
  • corepack pnpm --filter acp-extension-codex typecheck — passed.
  • corepack pnpm --filter acp-extension-codex exec vitest run src/__tests__/CodexACPAgent/CodexAcpClient.test.ts -t "cancels a compact slash command without waiting for compaction completion" --no-file-parallelism — passed.
  • corepack pnpm --filter @lody/components exec vitest run tests/session-context-compaction.test.ts — 2 tests passed.
  • corepack pnpm --filter @lody/components typecheck — passed.
  • The full Codex ACP client test file passed 100 tests; 2 unrelated existing Windows path-normalization tests failed.
  • git diff --check — passed.

Context handoff

Instructions for reviewing agents

  • Review focus: Inspect packages/acp-extension-codex/src/CodexAcpServer.ts cancellation handling and CodexCommands.ts compaction lifecycle callbacks, then verify the parent submodule pointer and session UI stop gate.
  • Decisions to challenge: Confirm that cancelling the owning ACP prompt is the correct recovery for a non-turn thread/compact/start operation, and that a late thread/compacted notification remains harmless.
  • Plausible failures / evidence gaps: The focused regression and typechecks pass; the full Codex ACP file retains 2 pre-existing Windows path-normalization failures, and packaged Electron clipboard/runtime smoke coverage was not applicable to this change.

Authoring context

  • User goal / directives: Make interrupting context compaction recover the session instead of leaving the ACP prompt permanently active.
  • Constraints / non-goals: Keep ordinary turn interruption unchanged; do not alter Codex compaction protocol messages or redesign session cancellation; do not change unrelated Windows path behavior.
  • Risk-bearing decisions: Only a prompt marked as actively running compaction is cancelled directly; ordinary active turns continue through the existing turn-id interrupt path.
  • Destructive or irreversible behavior: Cancellation sends no destructive filesystem operation; it aborts the local prompt wait and leaves any provider-side compaction completion notification to be safely ignored or consumed.
  • Deliberately not done or tested: No packaged Electron end-to-end run was performed; provider-side cancellation timing is covered by the deterministic ACP mock regression and the existing late-notification path.
  • Unknowns / confidence: Confidence is high for the reported ACP deadlock because the regression reproduces the missing-turn cancellation shape; residual risk is limited to provider-specific behavior outside the adapter's deterministic contract.

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

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/components/src/components/sessions/session-chat-interface.tsx Outdated

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

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

isGoalActive,
canPauseGoal,
}: CanStopAgentOptions): boolean =>
(isContextCompacting && activeAssistantTurnId != 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 Use live turn ownership before exposing Stop

After a daemon restart or interrupted stream leaves both an in-progress compaction marker and an unfinished assistant entry in durable history, this condition still exposes Stop even though nothing is cancellable. Fresh evidence beyond the prior comment is that resolveActiveAssistantTurnId accepts any history entry lacking finished/endedAt, whereas cancelSession requires that ID in its in-memory active/execution maps and otherwise returns success without updating history; consequently, clicking Stop silently no-ops and the button remains permanently visible. Base this branch on a live cancellability signal, or finalize the stale history when cancellation finds no matching runtime.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Acknowledged. The fix gates the compaction Stop branch on a non-null activeAssistantTurnId, but as noted that ID is history-derived and may not be live-cancellable after a daemon restart or interrupted stream. Finalizing stale history at cancellation time would require resolving the user turn ID in cancelSession and routing it through markDispatchCancelled, which is a backend change beyond this PR's frontend scope. Happy to take it on as a follow-up if the maintainers agree the live-cancellability signal or stale-finalization path is the right direction.

UniversePeak and others added 3 commits September 5, 2026 15:18
Chatgpt-codex-connector[bot] P2 review (2026-09-01T16:05:21Z) on
session-chat-interface.tsx:3348: when a pending/in-progress compaction
marker remains in history but its assistant entry is already finished
(restart, interrupted notification stream), isContextCompacting stays
true while activeAssistantTurnId is null. The previous canStopAgent
branch showed Stop in that state, but handleStop rejects the click as
missing_active_turn, leaving an idle session with a permanently
nonfunctional Stop button.

Gate the compaction case on a cancellable assistant turn by extracting
canStopAgentEnabled into session-context-compaction.ts and adding
regression tests.

Model: gpt-5.6-luna

@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: 1fc4d0143c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

this.deps.logger.debug(
`[${sessionId}] Finalizing stale unfinished turn ${turnId} after stop request found no live runtime`
);
this.deps.clearSessionActivePresence(sessionId);

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 Preserve newer turns while repairing stale compaction

When a delayed or multi-client cancellation for an old compaction arrives after a newer turn has started, this branch is entered because the active/execution IDs do not match the requested old turn, but it still clears the session-wide presence and finalizeCancelledTurn sets the whole session to idle. SessionActivePresenceController.setPhase is a no-op after clear, so the newer turn remains invisible and loses its Stop control and heartbeat for the rest of its execution. Only perform these session-wide cleanup operations when there is no current turn; otherwise restrict the repair to the stale history entry.

AGENTS.md reference: apps/cli/src/session/AGENTS.md:L107-L114

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: 694e6c0264

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +4718 to +4722
await this.finalizeCancelledTurn({
sessionId,
sessionDoc,
turnId,
reportTurnError: false,

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 Avoid canceling a newer queued turn during stale repair

When this stale compaction coexists with a newer pending user turn that has not yet acquired a runtime, currentTurnId is still null, so this calls finalizeCancelledTurn without the stale turn's userTurnId. Its markDispatchCancelled fallback then selects existingMeta.latestUserMsgId, marks that unrelated newer turn canceled, and advances lastHandledUserMsgId, causing the newly submitted prompt to be skipped. Restrict this path to repairing the stale assistant entry and clearing its cancel request rather than invoking a finalizer that infers ownership from current dispatch metadata.

AGENTS.md reference: apps/cli/src/session/AGENTS.md:L152-L157

Useful? React with 👍 / 👎.

@UniversePeak

Copy link
Copy Markdown
Author

I pushed a follow-up that keeps the compaction Stop control tied to a live cancellable turn and adds a recovery path for stale history. When a compaction marker and unfinished assistant entry survive without a live runtime, cancellation now marks the compaction failed, finalizes the assistant entry, clears the session state, and leaves newer turns untouched. The adapter dependency was also rebased onto its current main while retaining the non-turn prompt cancellation fix. Focused tests passed, and the PR is mergeable again.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] 压缩上下文终端后卡死

3 participants