Version: eve 0.39.1 (also 0.39.0)
What happens
ToolDefinition.approvalKey is documented as letting the runtime record a compound, input-derived key into the session's approved-tools set instead of the bare tool name (runtime/types.d.ts), and the harness honors it: resolveApprovalKeyFromTools in harness/tool-loop.js reads approvalKey off the resolved tool map, and approval recording resolves through it.
But for tools produced by a dynamic resolver (e.g. step.started), the conversion in context/dynamic-tool-lifecycle.js strips the field:
function toHarnessToolDefinition(e, t) {
return {
description: t.description,
execute: createToolExecuteWithAuth({...}),
inputSchema: toInputSchema(t.inputSchema),
name: e,
approval: t.approval,
// approvalKey is not copied
outputSchema: toOutputSchema(t.outputSchema),
...(t.toModelOutput === void 0 ? {} : { toModelOutput: t.toModelOutput }),
};
}
So a dynamic tool that declares approvalKey gets its approvals recorded under the bare tool name. The failure is silent: an approval policy that checks ctx.approvedTools for the scoped key never matches, and the UX degrades to re-prompting on every call — or worse, a policy written against bare names grants more than the author scoped.
Repro
- Return a tool with
approvalKey: (input) => \mytool@${input.scope}`from astep.started` dynamic resolver.
- Trigger it, approve the request.
- Inspect session state /
ctx.approvedTools on the next call: it contains "mytool", not "mytool@...".
Expected
approvalKey survives the dynamic-tool conversion, matching the behavior documented on ToolDefinition and already implemented in the harness read path.
Suggested fix
One field-passthrough in toHarnessToolDefinition — we run this as a patch in production and it restores the documented behavior:
approval: t.approval,
...(t.approvalKey === void 0 ? {} : { approvalKey: t.approvalKey }),
Possibly related: replayDynamicSessionTools in the same module also rebuilds tools without approval/approvalKey — unreachable for step-resolved tools, but worth aligning while in there.
Context: we use input-scoped approvals to let one human approval cover a chain of GitHub-authoring writes bound to a (repo, branch) target; without the passthrough the scoping silently never engages.
Version: eve 0.39.1 (also 0.39.0)
What happens
ToolDefinition.approvalKeyis documented as letting the runtime record a compound, input-derived key into the session's approved-tools set instead of the bare tool name (runtime/types.d.ts), and the harness honors it:resolveApprovalKeyFromToolsinharness/tool-loop.jsreadsapprovalKeyoff the resolved tool map, and approval recording resolves through it.But for tools produced by a dynamic resolver (e.g.
step.started), the conversion incontext/dynamic-tool-lifecycle.jsstrips the field:So a dynamic tool that declares
approvalKeygets its approvals recorded under the bare tool name. The failure is silent: an approval policy that checksctx.approvedToolsfor the scoped key never matches, and the UX degrades to re-prompting on every call — or worse, a policy written against bare names grants more than the author scoped.Repro
approvalKey: (input) => \mytool@${input.scope}`from astep.started` dynamic resolver.ctx.approvedToolson the next call: it contains"mytool", not"mytool@...".Expected
approvalKeysurvives the dynamic-tool conversion, matching the behavior documented onToolDefinitionand already implemented in the harness read path.Suggested fix
One field-passthrough in
toHarnessToolDefinition— we run this as a patch in production and it restores the documented behavior:Possibly related:
replayDynamicSessionToolsin the same module also rebuilds tools withoutapproval/approvalKey— unreachable for step-resolved tools, but worth aligning while in there.Context: we use input-scoped approvals to let one human approval cover a chain of GitHub-authoring writes bound to a (repo, branch) target; without the passthrough the scoping silently never engages.