Skip to content

fix(harness): resolve(execute) resumes the pre-trigger chain with mutated arguments#520

Merged
ytallo merged 4 commits into
feat/harness-conformance-e2efrom
fix/506-release-runs-original-args
Jul 20, 2026
Merged

fix(harness): resolve(execute) resumes the pre-trigger chain with mutated arguments#520
ytallo merged 4 commits into
feat/harness-conformance-e2efrom
fix/506-release-runs-original-args

Conversation

@ytallo

@ytallo ytallo commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Fixes #506.

Releasing a hook-held call with harness::function::resolve {action: "execute"} executed the ORIGINAL model arguments: hook-chain mutations — from hooks before the holding hook and after it — were not applied.

Root cause

The execute arm in harness/src/deferred.rs recovered arguments via find_call_arguments from the transcript (the model's original function_call), and pre-hold mutations existed only in-memory inside run_pre_triggerPreTriggerOutcome::Hold returned {held_by, annotations} without the accumulated args, and nothing persisted them.

Fix — design (a): resume through the remaining pipeline

harness.md § function::resolve explicitly promises "the pre_trigger chain resumes after the holding hook", so that's what release now does:

  • Hold-point-mutated arguments are persisted on the durable call checkpoint (CallCheckpoint.held_arguments, serde-defaulted for old records).
  • Release runs the chain slice strictly after the holder over those arguments (new testable chain_slice helper), then re-applies filesystem_scope::inject (session grants ∪ req.fs_scope) so a hook rewrite can never widen scope.
  • A resumed Deny appends a hook_denied error result; a resumed Hold re-parks under the new holder with the mutations so far; if the holder was unbound since the hold, no hooks run (safe resume point unknowable — degrades to executing the checkpointed args).
  • Transcript recovery kept only as a legacy fallback for records parked before the field existed.

Verification

Note for review

Docs say execute "re-enqueues the turn"; the (pre-existing) implementation runs the release inline in deferred.rs. Observable semantics now match the docs; the mechanism difference is worth a doc touch-up later.

@vercel

vercel Bot commented Jul 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
workers Ready Ready Preview, Comment Jul 20, 2026 2:23pm
workers-tech-spec Ready Ready Preview, Comment Jul 20, 2026 2:23pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4fa5bf06-0abe-4af0-a826-567b78cb2545

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/506-release-runs-original-args

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

skill-check — worker

0 verified, 46 skipped (no docs/).

Layer Result
structure
vale
ai
render

Four for four. Nicely done.

ytallo added 3 commits July 20, 2026 10:31
A call held by a pre_trigger hook and released with
harness::function::resolve {action: "execute"} ran the target with the
model's ORIGINAL arguments recovered from the transcript — mutations from
hooks that ran before the holder were dropped, and the remaining chain
never resumed, breaking the harness.md § function::resolve promise that
the pre_trigger chain resumes after the holding hook.

- PreTriggerOutcome::Hold now carries the arguments as mutated up to the
  hold; the loop persists them on the call's checkpoint (held_arguments),
  so they survive the park durably. Post-trigger holds checkpoint the
  fully pre-mutated args for the same reason.
- The execute release path prefers the checkpointed arguments (transcript
  recovery remains the fallback for records written before the field
  existed), resumes the pre_trigger chain AFTER the holder — hooks up to
  and including it already ran, so re-running would double-apply
  mutations and re-hold — and handles a resumed Deny/Hold like the loop.
  The filesystem scope stamp is still re-applied after the resumed chain.
- A holder no longer bound at release runs no further hooks: the chain
  shape changed under the hold and the safe resume point is unknowable.

Fixes #506
The fix on this branch makes the scenario pass; --scenario all (and the
per-PR CI job) now executes it. Verified locally: C-E2E-001, C-E2E-002,
C-E2E-506 all pass.
A pre-trigger hook returning decision:"hold" WITH mutations had them
discarded at parse (parse_output mapped "hold" to a data-less outcome),
so the approval-gate pattern (hold AND stamp validated context) was
impossible. HookOutcome::Hold now carries HookMutations via a shared
parse_mutations helper, and run_pre_trigger folds the holding hook's
argument rewrite into the effective arguments before parking. Those args
ride the call checkpoint added for #506 (fix/506-release-runs-original-args,
which this is based on), so the resolve(execute) release runs with the
gate's mutation.

Unquarantines C-E2E-505, now a regression gate.

Fixes #505
@ytallo
ytallo merged commit 1df76d6 into feat/harness-conformance-e2e Jul 20, 2026
39 checks passed
ytallo added a commit that referenced this pull request Jul 21, 2026
Composes the two features that collided on the resolve(execute) release
path: the pre-trigger chain re-run from #520 computes the (possibly
mutated) arguments first, and main's fire-and-forget subagent spawn
branch now consumes those arguments. The parked-spawn PendingInfo tail
in subagent.rs gives way to main's spawned_result, and the spawn Done
checkpoint carries held_arguments: None.
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.

1 participant