Summary
When a call held by a pre-trigger hook is released with
harness::function::resolve { action: "execute" }, the function executes with
the original arguments from the model's tool call. Argument mutations from
the hook chain are not applied on the release path — neither mutations made by
hooks that ran before the holding hook, nor mutations from hooks positioned
after it.
Observed live on harness 1.3.1 (engine 0.21.4, macOS arm64), with both
orderings tested explicitly.
Reproduction
Setup: two pre-trigger hooks bound to demo::write:
- an approval gate (returns
hold for this function),
- a scope injector that mutates args (
continue + args with __scope added).
Case A — injector at priority 20, gate at priority 10 (injector runs first):
- Turn calls
demo::write; injector mutates args; gate holds.
harness::function::resolve { action: "execute" }.
demo::write executes without the injector's mutation — the pre-hold
mutation is lost.
Case B — gate first, injector after:
- Gate holds before the injector runs.
- Release with
action: "execute".
- The remaining hook chain does not (observably) re-apply mutations: the
function again executes with the original arguments.
In both cases the function body received the raw model-supplied args, so a
function that fails closed on a missing hook-injected field (as ours does)
errors after an approved release.
Expected
Either of these would be workable, but the current behavior is neither:
- release re-dispatches through the remaining hook pipeline with mutations
applied (matching the docs' framing that resolve "resumes through the
remaining hook pipeline"), or
- release executes the checkpointed call as mutated up to the hold point.
Why it matters
Any tenancy/scoping model where hooks inject validated context into tool calls
breaks on the approval path: exactly the calls important enough to need
approval are the ones that lose their injected scope. Combined with the holding
hook's own mutations being discarded (#505), there is currently no
way to get hook-injected arguments into a held-then-released call.
Workaround we use
We stopped releasing with action: "execute" for confirmed writes: our backend
performs the confirmed write itself (with full context) and resolves the held
call with action: "deliver" + resultJson, so the parked run resumes on a
result we produced. This works well, but it means the "release and let the
harness execute" model is effectively unusable with hook-injected arguments.
Summary
When a call held by a
pre-triggerhook is released withharness::function::resolve { action: "execute" }, the function executes withthe original arguments from the model's tool call. Argument mutations from
the hook chain are not applied on the release path — neither mutations made by
hooks that ran before the holding hook, nor mutations from hooks positioned
after it.
Observed live on
harness1.3.1 (engine 0.21.4, macOS arm64), with bothorderings tested explicitly.
Reproduction
Setup: two pre-trigger hooks bound to
demo::write:holdfor this function),continue+ args with__scopeadded).Case A — injector at priority 20, gate at priority 10 (injector runs first):
demo::write; injector mutates args; gate holds.harness::function::resolve { action: "execute" }.demo::writeexecutes without the injector's mutation — the pre-holdmutation is lost.
Case B — gate first, injector after:
action: "execute".function again executes with the original arguments.
In both cases the function body received the raw model-supplied args, so a
function that fails closed on a missing hook-injected field (as ours does)
errors after an approved release.
Expected
Either of these would be workable, but the current behavior is neither:
applied (matching the docs' framing that resolve "resumes through the
remaining hook pipeline"), or
Why it matters
Any tenancy/scoping model where hooks inject validated context into tool calls
breaks on the approval path: exactly the calls important enough to need
approval are the ones that lose their injected scope. Combined with the holding
hook's own mutations being discarded (#505), there is currently no
way to get hook-injected arguments into a held-then-released call.
Workaround we use
We stopped releasing with
action: "execute"for confirmed writes: our backendperforms the confirmed write itself (with full context) and resolves the held
call with
action: "deliver"+resultJson, so the parked run resumes on aresult we produced. This works well, but it means the "release and let the
harness execute" model is effectively unusable with hook-injected arguments.