Skip to content

fix(actions): keep prepared review alive across lease handoff - #120

Merged
jerelvelarde merged 6 commits into
CopilotKit:mainfrom
kvnloo:fix/prepare-lease-loss-no-autodeny
Oct 5, 2026
Merged

jerelvelarde merged 6 commits into
CopilotKit:mainfrom
kvnloo:fix/prepare-lease-loss-no-autodeny

Conversation

@kvnloo

@kvnloo kvnloo commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Problem

AgentService.prepare() auto-denies an awaiting_review proposal when checkpointing actionId fails.

That cleanup is correct for a durable pause/cancel, but lease loss can abort the same worker signal. Treating the signal itself as proof of user cancellation can therefore destroy a proposal the replacement worker needs to recover.

Change

Base proposal cleanup on the persisted task status, not the abort signal alone.

On lease takeover:

  • the proposal remains awaiting_review;
  • the new worker reuses the same deterministic proposal;
  • no duplicate proposal is created.

On durable pause/cancel:

  • the orphaned proposal is denied;
  • the existing ownership, hash, account-binding, and execution checks remain unchanged.

Tests

Focused regressions cover:

  • lease takeover with an already-aborted worker signal;
  • replacement-worker reuse of the deterministic proposal;
  • durable pause and cancel cleanup.

Verification

Reviewer verification by @jerelvelarde:

  • 30 prepare-lease-loss/action/workflow/engine tests passed locally;
  • root TypeScript and changed-file Biome checks passed;
  • all seven GitHub CI checks were green.

Integration limits

No live provider writes were performed. This changes only recovery/cleanup classification; review schema, proposal keys, and approval execution semantics are unchanged.

@jerelvelarde

Copy link
Copy Markdown
Collaborator

Reviewed head 94f7f6987efa.

This is a valuable recovery fix, but the new context.signal.aborted condition still conflates a real pause/cancel with lease loss. In TaskWorker.run(), the heartbeat aborts the same controller when lease renewal returns no row or throws. If that happens after the proposal is created but before checkpoint, this branch still denies the review and the taking-over worker encounters the original failure.

Please base proposal cleanup on the durable task's actual pause/cancel state or an explicit abort reason, rather than the signal alone. Add a regression through the real worker heartbeat: lose the lease while preparing a proposal, observe an aborted signal, and assert the proposal survives for the replacement worker; keep the real pause/cancel denial case. The current new tests manufacture lease loss with an un-aborted signal and do not cover that path.

Current GitHub checks pass, but this is a static finding in the proposed cleanup condition; I did not rerun tests.

Changed code: apps/server/src/engine/service.ts:717.

kvnloo commented Oct 3, 2026

Copy link
Copy Markdown
Contributor Author

Addressed jerelvelarde's lease-heartbeat counterexample in 95d69610b4d3.

The cleanup decision no longer uses context.signal.aborted. On checkpoint failure it reads the durable task row and denies the orphaned proposal only when the task is actually paused or cancelled.

The regression now exercises the exact distinction:

  • aborted worker signal + durable task still running → proposal remains awaiting_review for takeover;
  • durable paused / cancelled task → orphaned proposal is denied.

This keeps heartbeat/worker-stop aborts recoverable without weakening explicit user stop semantics. CI for the new head is queued/running or reported below; I have not claimed a local run.

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

No blocking findings. Checking the persisted task status correctly distinguishes a lease takeover from a user pause/cancel, even when both abort the old worker's signal. A takeover retains one deterministic proposal for the successor; durably paused/cancelled tasks still clean up the orphaned review. Existing action ownership, hashes, account binding, and execution checks are preserved. This is a focused reliability fix appropriate for the template.

Validation: all 30 prepare-lease-loss/action/workflow/engine tests pass locally, including takeover with an already aborted signal and both pause/cancel cases. Root TypeScript and changed-file Biome checks pass. All seven GitHub checks pass. No live provider writes were performed.

Minor documentation note: the description still says cleanup is based on the abort signal; the final implementation correctly uses durable task status. Please update that explanation.

@jerelvelarde
jerelvelarde merged commit 6d8a084 into CopilotKit:main Oct 5, 2026
7 checks passed
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