Skip to content

🧾 fix: Commit Resume Provenance With the Approval Claim - #16430

Open
berry-13 wants to merge 3 commits into
devfrom
followup/120-13811-bound-post-ack-job-store-writes-in-resumea
Open

berry-13 wants to merge 3 commits into
devfrom
followup/120-13811-bound-post-ack-job-store-writes-in-resumea

Conversation

@berry-13

@berry-13 berry-13 commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

Pull Request

Summary

When a user answers an ask_user_question or edits, rejects or answers a tool approval, ResumeAgentController wins the approval claim (approvals.resolve, which spends the pending action and flips the job to running), sends the 200 ACK, and only then writes the user-submitted provenance paths to the job store with a separate, unbounded updateJob. ioredis can queue that command indefinitely on a half-open socket or in Cluster mode, so the continuation never starts, the failed-resume cleanup never runs, and a retry is answered 409 because the action is spent.

The provenance paths are already computed before the claim, and the claim's JobMetadataPatch already carries both fields, so they now ride in the claim's own CAS alongside the owner capability fields. The post-ACK write is gone: provenance commits atomically with the transition that spends the action, and a job-store failure there happens before the ACK, where the existing claim error path answers 500 and releases the concurrency slot.

The one exit between the claim and the ACK is a scheduled resume whose schedule changed under it: the fence rejects it and the job is aborted without the decision ever being applied. abortJob now takes the provenance to publish, and that path passes the job's pre-claim values, so the aborted response does not mark the original model output as user-authored and no separate rollback write is needed.

Addresses berry-13#120, a deferred finding from #13811, and berry-13#166.

Type of change

  • Bug fix

Testing

Tested environments/configuration:

  • Node 24; controller specs through supertest; approval lifecycle against the in-memory store; RedisJobStore transition against real Redis in the cache integration workflow.

Automated tests:

  • api/server/controllers/agents/__tests__/resume.spec.js: the provenance cases now assert the paths arrive in the approvals.resolve patch and that no post-ACK updateJob carries them; approve-only resumes send no provenance; a schedule-fence rejection passes the pre-claim provenance to abortJob. 151 passed; the provenance cases fail against the previous controller.
  • e2e/specs/mock/scenarios/approval-provenance.spec.ts (@scenario:an-edited-approval-keeps-its-user-submitted-provenance): edits a paused MCP tool call in the mock harness, waits for the resumed run to execute the edit, and reads the saved reply through /api/messages, which marks the edited arguments as user-submitted. The shared approval helpers moved to tool-approvals.helpers.ts so the scenario reuses them.
  • packages/api/src/stream/__tests__/steering.spec.ts: an abort given caller provenance publishes it instead of the job record's.
  • packages/api/src/stream/__tests__/pendingAction.spec.ts: resolving through GenerationJobManager.approvals persists both provenance shapes on the resumed job.
  • packages/api/src/stream/__tests__/RedisJobStore.stream_integration.spec.ts: a requires_action to running transition with a provenance patch reads both shapes back from Redis (runs in CI; no local Redis was available).
  • ESLint, Prettier, import sorting and tsc --noEmit for packages/api clean.

Screenshots / recordings

No user-facing change.

Risk / compatibility

The provenance fields move from a follow-up HSET into the claim's transition patch; both go through serializeJob, so the stored shape is unchanged and readers need no change. No new configuration.

Checklist

  • I reviewed my own changes
  • Relevant tests have been added or updated
  • Existing relevant tests pass
  • The change does not introduce new warnings or errors

Copilot AI lite review requested due to automatic review settings September 28, 2026 07:08
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 28, 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-30T08:23:24.022137Z 86465f4 New commits
🔒 Security Review ✅ Completed 2026-09-28T07:12:40.574859Z d38da49 PR opened
ℹ️ 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.

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

Failure-finalization job-store operations remain unbounded and may still block cleanup and slot release.

Review effort: Lite
Findings: 1 High severity · 1 Low severity

Open (2)
What changed in this PR

This PR bounds post-ACK provenance writes during agent resume to prevent indefinite stalls during Redis outages.

Changes:

  • Adds a five-second timeout around provenance persistence.
  • Adds regression coverage for stalled job-store writes.
File Description
api/​server/​controllers/​agents/​resume.js Applies the provenance-write timeout.
api/​server/​controllers/​agents/​__tests__/​resume.spec.js Tests timeout failure handling and slot release.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread api/server/controllers/agents/resume.js Outdated
Comment thread api/server/controllers/agents/resume.js 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: d38da4957d

ℹ️ 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 api/server/controllers/agents/resume.js Outdated
Comment thread api/server/controllers/agents/resume.js Outdated
Comment thread api/server/controllers/agents/resume.js Outdated
@berry-13
berry-13 force-pushed the followup/120-13811-bound-post-ack-job-store-writes-in-resumea branch from d38da49 to 7de0d9e Compare September 28, 2026 08:24
@berry-13 berry-13 changed the title ⏳ fix: Bound Post-ACK Resume Provenance Write 🧾 fix: Commit Resume Provenance With the Approval Claim Sep 28, 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: c8935c5830

ℹ️ 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 +1499 to +1500
...(userSubmittedPaths.length > 0 && { userSubmittedPaths }),
...(userSubmittedMessageFieldPaths.length > 0 && { userSubmittedMessageFieldPaths }),

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 Avoid attributing content when the schedule fence rejects

When a scheduled approval contains an edit, response, rejection reason, or answer and finalizeScheduleResumeClaim subsequently returns false or throws because the schedule changed, these fields have already been persisted by the approval CAS. The controller then calls abortJob without ever running resumeCompletion; abortJob reconstructs the pre-decision chunks but copies this new provenance into its final response, so, for example, the original model-generated tool arguments are mislabeled as user-authored. Keep the decision provenance distinguishable until it is applied, or clear/transform it on this abort path so cancellation and restored-session attribution remain accurate.

AGENTS.md reference: AGENTS.md:L42-L45

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in dab931f. Confirmed abortJob stamps jobData provenance onto the aborted response. On a schedule-fence rejection the controller now restores the job's pre-claim provenance (createdAt-fenced updateJob) before abortJob, so the pre-decision chunks are not attributed to the user. Covered by resume.spec.js 'restores pre-claim provenance before aborting a resume the schedule fence rejected', which fails without the restore. The residual case where the restore write itself fails is tracked at berry-13#166.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Correction to the reply above: the restore-updateJob approach from dab931f (74bd0e0 after the rebase) was replaced in d30fa44. No pre-claim restore write remains; on a schedule-fence rejection the controller passes the pre-claim provenance from job.metadata to GenerationJobManager.abortJob, which publishes it in the same transition as the abort, so there is no separate write that can fail. Covered by steering.spec.ts (abort publishes caller provenance) and resume.spec.js (abortJob receives the pre-claim provenance, no updateJob). berry-13#166 is closed by that change; competing abort winners (schedule or account deletion, Stop) are tracked at berry-13#167.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Current state on 86465f4: the caller-passed abortJob provenance from d30fa44 is gone too. The claim CAS stores the pre-claim provenance and abortJob selects it from the job record whenever the resumed provider never started, for any abort caller (cf8ef09). No restore or rollback write exists.

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

ℹ️ 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 api/server/controllers/agents/resume.js Outdated
@berry-13
berry-13 force-pushed the followup/120-13811-bound-post-ack-job-store-writes-in-resumea branch from a78502d to d30fa44 Compare September 28, 2026 11:14

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

ℹ️ 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 api/server/controllers/agents/resume.js Outdated
Comment on lines +1697 to +1700
provenance: {
userSubmittedPaths: job.metadata.userSubmittedPaths,
userSubmittedMessageFieldPaths: job.metadata.userSubmittedMessageFieldPaths,
},

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 provenance when a competing schedule abort wins

When a schedule or account is deleted after approvals.resolve commits the decision provenance but before this fence check finishes, deleteScheduleForOwner independently reaches abortScheduledJob → abortJob without this override (packages/api/src/schedules/service.ts:611-643,1359-1364). If that abort wins the running → aborted transition, it publishes the newly claimed edit/answer paths against the pre-decision chunks; this call then receives already_settled and cannot correct the final event, so cancellation can still label model-authored content as user-authored. Fresh evidence beyond the earlier resolved thread is this competing schedule-deletion abort path; preserve the pre-claim provenance durably for every possible abort winner or pass it through that path as well.

AGENTS.md reference: AGENTS.md:L42-L45

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Valid, and the same root as a pre-existing dev window: abortJob publishes the job record's HITL provenance on content rebuilt from pre-decision chunks, so any abort between the provenance commit and resumeCompletion applying the decision mislabels it (on dev, a Stop during initializeClient after the post-ACK write). This PR moves the start of that window from the ACK to the claim; the controller's own fence-rejection abort passes pre-claim provenance, but competing abort winners (schedule/account deletion, Stop) need abortJob to publish only applied-decision provenance. Tracked as the shared fix at berry-13#167.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in cf8ef09 instead of deferring. The approval claim CAS now also records the provenance it replaces (preResumeProvenance), and abortJob publishes that whenever the post-terminal-CAS record shows the resumed provider never crossed its start CAS (providerExecutionStartedId, which the claim clears). That covers every abort winner (this fence, deleteScheduleForOwner via abortScheduledJob, account deletion, Stop) without a caller override. steering.spec.ts claim-then-abort cases fail without it; scenario an-edited-approval-keeps-its-user-submitted-provenance passes on 86465f4 (desktop light/dark, mobile).

…pre-claim paths from any unapplied abort

The claim CAS now records the provenance it replaces. An abort that lands
before the resumed provider crosses its start CAS publishes those paths, so
schedule deletion, account deletion and user stops no longer label
model-authored chunks as user-submitted.
@berry-13
berry-13 force-pushed the followup/120-13811-bound-post-ack-job-store-writes-in-resumea branch from d30fa44 to 86465f4 Compare September 30, 2026 08:12

@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: 86465f45f1

ℹ️ 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 +4821 to +4824
const provenance =
!providerStarted && jobData.preResumeProvenance != null
? jobData.preResumeProvenance
: jobData;

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 Tie abort provenance to the content actually published

When an abort wins after beginProviderExecution but before resumeClient() applies an edited approval, providerExecutionStartedId is already set (resume.js:1891-1902) while abortContent still contains the original model arguments, so this branch labels those arguments as user-authored. Conversely, the abort route can stamp a claimed ask_user_question answer into the content while this marker is still unset, causing this branch to omit the answer's provenance. Fresh evidence on this head is the new steering test that calls beginProviderExecution and then abortJob without applying any decision content, yet expects the claimed provenance; select provenance based on the content actually published rather than provider-start status.

AGENTS.md reference: AGENTS.md:L49-L52

Useful? React with 👍 / 👎.

Comment on lines +4821 to +4824
const provenance =
!providerStarted && jobData.preResumeProvenance != null
? jobData.preResumeProvenance
: jobData;

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 Persist the provenance selected for the abort

On a pre-provider abort, these local variables make the live final event use preResumeProvenance, but AbortResult.jobData still contains the newly claimed paths. The abort route's beforePublish callback saves pendingAbortResult.content with jobData.userSubmittedPaths and jobData.userSubmittedMessageFieldPaths (api/server/routes/agents/index.js:755-815), so after a competing schedule/account/user abort the database row still labels the original model content as user-authored and a reload disagrees with the SSE. Fresh evidence on this head is that the new provenance selection is confined to final-event construction; return the selected provenance to persistence or build both outputs from the same response message.

AGENTS.md reference: AGENTS.md:L49-L52

Useful? React with 👍 / 👎.

@danny-avila danny-avila added the 🗺️ Agent Runtime codegraph: the taxonomy area this belongs to (classifier, confidence ≥ 0.9) label Sep 30, 2026

This branch has not been deployed

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

Labels

🗺️ Agent Runtime codegraph: the taxonomy area this belongs to (classifier, confidence ≥ 0.9)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants