Found while fixing #7448 (PR #7660). Out of scope there — that card is the observability of ctx.log.{info,warn,error}; this is a fourth method that does not exist. Filed rather than folded in, per Prime Directive #10. Unassigned — recording only.
The gap
ctx.log.debug is declared on three surfaces and implemented on none.
| Surface | Says | Anchor (main @ 245d1dc) |
|---|
| CLI capability extractor | matches debug, grants the log capability | packages/cli/src/utils/extract-hook-body.ts:56 — /ctx\.log\.(?:info|warn|error|debug)\b/ |
| Docs | lists ctx.log.info / warn / error / debug → log | content/docs/automation/hook-bodies.mdx:317 |
Sandbox ScriptContext | declares info / warn / error only | packages/runtime/src/sandbox/script-runner.ts:243 |
| QuickJS VM install | installs info / warn / error only | packages/runtime/src/sandbox/quickjs-runner.ts:639 — for (const level of ['info', 'warn', 'error'] as const) |
So an author writes ctx.log.debug(…), the CLI reads it, infers ['log'] and emits a body whose declared capability is satisfied — and the call then throws inside the VM.
Measured
Hook body ctx.log.debug('hi') with capabilities: ['log'], run through hookBodyRunnerFactory + QuickJSScriptRunner with a logger that has a real debug method:
hook 'h' threw: TypeError: not a function
Not a silent drop — the hook fails. Under onError: 'abort' that aborts the write.
Why this is the crypto.hash shape again
script-runner.ts:249-258 documents the precedent in its own comment: crypto.hash carried a signature, a capability token and CLI build-time inference for a function the sandbox never installed, so the one call it typed threw inside the VM. It was removed rather than implemented (#4391, ADR-0049 enforce-or-remove). ctx.log.debug is the same shape one member over — and unlike hashing it has no security-surface argument against implementing it.
Decision needed (enforce-or-remove)
Enforcing looks right here (a body emitting debug-level diagnostics is exactly what --log-level debug is for), but it is a capability-surface call, not a bug fix, so it should be decided rather than assumed.
Dedup
Searched open issues for ctx.log, log.debug, capability log, extract-hook-body: no existing card. #7448 is the adjacent one and covers only the three installed methods.
Found while fixing #7448 (PR #7660). Out of scope there — that card is the observability of
ctx.log.{info,warn,error}; this is a fourth method that does not exist. Filed rather than folded in, per Prime Directive #10. Unassigned — recording only.The gap
ctx.log.debugis declared on three surfaces and implemented on none.main@245d1dc)debug, grants thelogcapabilitypackages/cli/src/utils/extract-hook-body.ts:56—/ctx\.log\.(?:info|warn|error|debug)\b/ctx.log.info / warn / error / debug→logcontent/docs/automation/hook-bodies.mdx:317ScriptContextinfo/warn/erroronlypackages/runtime/src/sandbox/script-runner.ts:243info/warn/erroronlypackages/runtime/src/sandbox/quickjs-runner.ts:639—for (const level of ['info', 'warn', 'error'] as const)So an author writes
ctx.log.debug(…), the CLI reads it, infers['log']and emits a body whose declared capability is satisfied — and the call then throws inside the VM.Measured
Hook body
ctx.log.debug('hi')withcapabilities: ['log'], run throughhookBodyRunnerFactory+QuickJSScriptRunnerwith a logger that has a realdebugmethod:Not a silent drop — the hook fails. Under
onError: 'abort'that aborts the write.Why this is the
crypto.hashshape againscript-runner.ts:249-258documents the precedent in its own comment:crypto.hashcarried a signature, a capability token and CLI build-time inference for a function the sandbox never installed, so the one call it typed threw inside the VM. It was removed rather than implemented (#4391, ADR-0049 enforce-or-remove).ctx.log.debugis the same shape one member over — and unlike hashing it has no security-surface argument against implementing it.Decision needed (enforce-or-remove)
debugto the VM install loop and toScriptContext['log']. Cheap;Logger.debug(message, meta)already exists on the contract (packages/spec/src/contracts/logger.ts:15), and after [finding] Hook body ctx.log output is unobservable — body-runner wires log: engineCtx?.logger with no fallback, so the declared ['log'] capability can silently produce nothing #7448 the capability is served from a realLoggerthat has it.debugfrom the extractor regex and the docs table, so the three surfaces agree on three methods.Enforcing looks right here (a body emitting debug-level diagnostics is exactly what
--log-level debugis for), but it is a capability-surface call, not a bug fix, so it should be decided rather than assumed.Dedup
Searched open issues for
ctx.log,log.debug,capability log,extract-hook-body: no existing card. #7448 is the adjacent one and covers only the three installed methods.