Skip to content

fix: retain session ownership until compaction cancellation completes - #618

Merged
wibus-wee merged 15 commits into
mainfrom
fix/stale-context-compaction-reconciliation
Sep 13, 2026
Merged

wibus-wee merged 15 commits into
mainfrom
fix/stale-context-compaction-reconciliation

Conversation

@wibus-wee

@wibus-wee wibus-wee commented Sep 11, 2026

Copy link
Copy Markdown
Member

Related issue

Follow-up to #573.

Problem / pressure

Stop during manual Codex /compact must keep both the adapter prompt and Lody's execution owner until native termination. The adapter previously cancelled only its local wait. Lody also interrupted its owner fiber immediately, entering history finalization before ACP completed.

Lody's existing finalizer already retained the runtime through a raw-request drain; this was not unconditional early owner release. Normal Stop now keeps the owner in the live prompt while retaining #571's five-second termination fallback for an unresponsive provider.

Summary

Update the Codex adapter to capture the compaction turn, interrupt it on Stop, and retain prompt ownership until native completion or connection closure. In Lody, Stop during an in-flight prompt with a ready ACP session sets the existing cancellation flag and sends provider cancel without interrupting the owner fiber. Cancellation history finalization then runs after ACP returns, before releasing execution ownership.

Adapter implementation: LodyAI/acp-extension-codex#41 (merge dependency). The submodule points to its published commit.

The active Stop branch sends provider cancel without interrupting its fiber, then starts the existing five-second raw-request drain. A pending prompt at the deadline triggers termination of the old session; connection closure lets the prompt reject naturally. Failed termination retains ownership until ACP ends. Stop and the cancellation finalizer share one drain promise, so repeated Stop cannot reset the deadline or terminate twice.

Steer checks cancellation both before submission and after its acceptance ACK. If Stop wins while that ACK is pending, the accepted steer cannot change the source invocation, settle it as handled, or take ownership. Lody marks that exact steer user turn canceled without changing dispatch pointers, then returns stale-turn; neither daemon nor client requeues it. The source remains the cancellation owner through provider terminal. Only a provably undelivered steer becomes a pending ordinary turn. No retry, reconciliation protocol or new barrier is introduced.

Visual explanation

sequenceDiagram
  participant Host as Lody
  participant ACP as Codex adapter
  participant Native as Codex app-server
  Host->>ACP: prompt /compact
  ACP->>Native: thread/compact/start
  Native-->>ACP: turn/started
  Host->>ACP: Stop
  Note over Host: Retain owner fiber and unfinished history
  ACP->>Native: turn/interrupt
  Native-->>ACP: interrupt ACK
  Note over ACP: Retain active prompt
  alt Native terminal within five seconds
    Native-->>ACP: turn/completed (interrupted)
    ACP-->>Host: prompt cancelled
  else Raw prompt still pending
    Host->>ACP: terminate old session
    Note over Host,ACP: Failed termination keeps waiting; no owner release
    ACP-->>Host: connection close rejects prompt
  end
  Host->>Host: persist terminal history, release execution
Loading

Before / after

Before After
Manual compaction could keep running after local cancellation. Stop interrupts its native turn and waits for terminal confirmation.
Lody interrupted the owner fiber and entered history finalization before draining ACP. Lody waits in the existing prompt; another dispatch remains pending until native completion and finalization.
Compaction process exit could leave a pending ACP prompt. Connection closure rejects the pending command.

Test plan

  • Adapter typecheck and 617 tests pass; 27 existing environment-gated tests are skipped. This integration follow-up does not modify fix: resolve TODO comments in json-lines-parser and root route #41.
  • Lody execution, dispatch-watcher and AgentClient suites pass 207 tests. Controlled completion covers /compact, Stop, a pending next message, then provider terminal. The steer race explicitly submits steer, withholds its applied ACK, sends Stop, and resolves acceptance: the steer becomes canceled, source history remains unfinished, invocation and dispatch ownership stay with the source, and no handled settlement or replay occurs. Provider terminal cancels the source before release; the steer remains terminal. The steer status assertion failed on ae24a723, where it remained pending_apply. Fake timers also cover the five-second termination deadline, withheld cancel ACK, repeated Stop without deadline reset, and failed termination retaining ownership. Existing external-interruption and steer admission regressions remain covered.
  • Real Codex 0.153.4 smoke passes for normal compaction and Stop: native interrupted precedes ACP cancelled.
  • Root typechecks, lint, formatting, i18n, docs and code-collab/platform/public boundary checks pass.
  • Full pnpm check passes all preceding workspace suites, then Electron reports 103/104 passing: the relay suite cannot import Electron because the local binary is not installed.

Context handoff

Instructions for reviewing agents

  • Review focus: Inspect Lody's Stop branch, post-ACK steer ownership check, shared cancellation drain and natural finalization, alongside the pinned adapter's native terminal boundary.
  • Decisions to challenge: Interrupt ACK cannot release a compact prompt. A successful steer ACK after Stop must terminalize that steer as canceled without handoff, dispatch pointer changes or replay; the source must retain its cancellation outcome.
  • Plausible failures / evidence gaps: If termination fails and ACP never completes, ownership stays held, preserving fix(cli): retain ACP prompt ownership after cancellation #571's refusal to reuse a busy provider. Native smoke used Codex 0.153.4; the Lody regression controls the ACP transport.

Authoring context

  • User goal / directives: Correct the compaction cancellation boundary and remove the expanding historical repair mechanism.
  • Constraints / non-goals: Keep existing CLI terminal settlement; do not migrate historical records or add renderer-driven history mutations.
  • Risk-bearing decisions: The ready in-flight Stop branch retains its owner fiber and applies the existing five-second recovery deadline independently of cancel ACK. Before-prompt/finalization teardown and external-interruption drain behavior remain covered by the owning suites.
  • Destructive or irreversible behavior: No data migration or bulk rewrite; ordinary terminal history persistence remains authoritative.
  • Deliberately not done or tested: Legacy stale-history cleanup remains out of scope; the local Electron relay test is environment-blocked.
  • Unknowns / confidence: Deterministic lifecycle tests and real native interruption agree on the cancellation boundary.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-12T18:09:58.161058Z cde039d New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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: 8a48169ada

ℹ️ 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 +2725 to +2728
.then((result) => {
if (!result || result.outcome === 'unknown') {
compactionReconciliationAttemptsRef.current.delete(attemptKey);
}

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 Retry reconciliation after the active turn disappears

When reconciliation returns active and that daemon subsequently exits before persisting the compaction's terminal update, this attempt key remains in the set permanently. The offline/online transition reruns the effect, but line 2717 suppresses the request to the replacement daemon, leaving the durable pending/in_progress item and its spinner stuck until the component is unmounted and reopened. Clear or invalidate active attempts when live ownership changes so restart recovery can actually reconcile the stale turn.

AGENTS.md reference: AGENTS.md:L82-L87

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: a357b744e4

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

'machine/acp-capabilities-refresh-cancel',
'session/cancel',
'session/live-status',
'session/reconcile-context-compaction',

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 Keep reconciliation off the fast control lane

When four stale sessions are mounted together, their reconciliation requests can occupy every slot in the four-request control pool: unlike the other fast handlers, each one awaits document acquisition, a history write, and waitUntilSynced() before returning. That queues session/cancel, session/dispatch-turn, status, and lifecycle requests behind potentially multi-second synchronization; because expiry is checked only after acquiring the lane, those user-control requests can time out or be discarded. Run this repair on the ordinary lane or acknowledge it before performing the durable work.

AGENTS.md reference: packages/loro-streams-rpc/AGENTS.md:L48-L55

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: 7e2680aedd

ℹ️ 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 apps/cli/src/lib/message-handler.ts Outdated
return response('unknown', { error: 'Session history is not synced with its owner.' });
}

const releaseBarrier = this.executionService.tryAcquireSessionRewriteBarrier(args.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 Requeue goal turns after releasing the rewrite barrier

When a set or resume goal request arrives after this barrier is acquired but before it is released, queueGoalTurn immediately tries continueSession; runVisibleSessionTurn returns rewrite-barrier, leaving claimed false, and lines 1304–1317 of session-execution-service.ts then delete the accepted pending goal and record a pre-prompt failure. The finally block only wakes SessionDispatchWatcher, which does not own pendingGoalTurnBySession, so the promised Lody-owned goal prompt never runs; explicitly retry or wake queued goal work when reconciliation releases the barrier.

AGENTS.md reference: packages/shared/AGENTS.md:L78-L83

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: 7fe6689323

ℹ️ 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 +2739 to +2742
.then((result) => {
if (!isDurableContextCompactionReconciliation(result)) {
compactionReconciliationAttemptsRef.current.delete(attemptKey);
}

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 Retry active results when the same daemon releases the turn

When this request returns active during the interval between host finalization and provider termination, deleting the attempt key does not itself rerender the component or rerun this effect. If the same daemon subsequently releases the turn without persisting a terminal compaction update, neither history, connectivity, nor the machine instance ID changes, so the stale spinner remains indefinitely. The new owner-instance key only fixes daemon replacement; fresh evidence in this revision is that session live-presence/ownership state is still absent from the effect dependencies. Trigger a retry when same-daemon session activity ends.

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: 5faa4d9f27

ℹ️ 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 apps/cli/src/lib/message-handler.ts Outdated
return response('retry', { error: 'The daemon still has live Session work.' });
}

if (!(await verifyOwnership())) {

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 Recheck live ownership after the awaited metadata read

When a durable activation is recovered while reconciliation is in flight, hasLiveSessionWork() can return false and then this awaited ownership lookup yields while the control-lane dispatch registers the recovered turn. Recovery reuses the deterministic assistant:<userTurnId> entry, so the following targeted update can mark the compaction failed in that now-active same turn. Recheck live work after the final await, or serialize this mutation with turn acquisition, before changing history.

AGENTS.md reference: apps/cli/src/lib/acp/AGENTS.md:L74-L78

Useful? React with 👍 / 👎.

@wibus-wee wibus-wee changed the title fix: reconcile stale context compaction fix: stop native Codex compaction on cancellation Sep 12, 2026
@wibus-wee wibus-wee changed the title fix: stop native Codex compaction on cancellation fix: retain session ownership until compaction cancellation completes Sep 12, 2026

@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: 88b43c948f

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

}
const ownedPromptRun = runtime.activePromptRun;
if (!ownedPromptRun || ownedPromptRun.turnId !== runtime.turnId) {
if (runtime.cancelRequested || !ownedPromptRun || ownedPromptRun.turnId !== runtime.turnId) {

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 Recheck cancellation after steer acknowledgement

When Stop arrives after this guard but while await steerRun.applied is pending, the new Stop path deliberately keeps the runtime alive, and the post-ack ownership check does not examine cancelRequested. A late successful acknowledgement therefore settles the stopped source turn as handled, transfers runtime and dispatch ownership to the steer, and then lets cancellation finalization cancel that new turn instead. Serialize Stop with steer acceptance or reject the ownership transfer when cancellation won this interval.

AGENTS.md reference: apps/cli/src/session/AGENTS.md:L58-L60

Useful? React with 👍 / 👎.

Keep the merged session guidelines below the documentation size limit without changing their contracts.

Model: gpt-6

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

ℹ️ 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 +1560 to +1564
// Provider acceptance forbids replay; Stop keeps the source cancellation owner.
return reject(
'stale-turn',
'Steer application arrived after cancellation or ownership changed'
);

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 Finalize late-accepted steers when Stop wins

When an acknowledged steer has already been submitted and Stop sets cancelRequested before its acceptance resolves, this branch returns stale-turn without changing the steer entry from pending_apply. The renderer promotes only no-active-turn responses, while dispatch deliberately skips pending_apply entries without a matching activation pointer, so this accepted-but-cancelled message remains permanently nonterminal and noneditable after the source turn releases. Mark the steer as cancelled without requeueing it or transferring ownership.

AGENTS.md reference: apps/cli/src/session/AGENTS.md:L58-L60

Useful? React with 👍 / 👎.

@wibus-wee
wibus-wee merged commit c0bd68f into main Sep 13, 2026
7 checks passed
@wibus-wee
wibus-wee deleted the fix/stale-context-compaction-reconciliation branch September 13, 2026 05:17
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.

1 participant