Skip to content

fix(runtime): install ctx.log.debug in the sandbox — the fourth level the CLI and docs already promise - #7908

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-7661-ctx-log-debug
Aug 12, 2026
Merged

fix(runtime): install ctx.log.debug in the sandbox — the fourth level the CLI and docs already promise#7908
hotlong merged 1 commit into
mainfrom
claude/issue-7661-ctx-log-debug

Conversation

@hotlong

Copy link
Copy Markdown
Contributor

Fixes#7661

ctx.log.debug was declared on three surfaces and implemented on none. An author who followed the documentation wrote a body whose declared capability was satisfied and whose call then threw TypeError: not a function inside the VM — under onError: 'abort', an aborted write rather than a missing log line.

Direction is the enforce arm of ADR-0049, per the ruling on the card: all four surfaces now agree on four methods.

The premise, measured before any code was written

The ruling hangs on two halves, both verified on origin/main @ d91fad5:

HalfVerdictEvidence
Logger.debug(message, meta) exists on the contract✅ holdspackages/spec/src/contracts/logger.ts:15 — declared, two-arg, alongside info/warn/error/fatal
the logger the log capability is served from carries a callable debug✅ holdsthe surface is built from opts.logger (body-runner.tsbuildBodyLogSurface, #7448), which all four app-plugin.ts construction sites populate with the kernel's ctx.logger = createLogger()ObjectLogger, whose debug (packages/core/src/logger.ts:401) writes at level debug through the real sink. Not absent, not a throwing stub, not a no-op. body-runner.ts:164 already calls opts.logger?.debug?.(…) on that same object — the [BodyRunner] hook fired line QA run #7439 observed at --log-level debug.

And the gap itself is still live on current main — this is not an already-fixed card:

  • packages/cli/src/utils/extract-hook-body.ts:56/ctx\.log\.(?:info|warn|error|debug)\b/log, debug still in the alternation
  • content/docs/automation/hook-bodies.mdx:317 — still teaches ctx.log.info / warn / error / debug
  • quickjs-runner.ts:639 — still ['info', 'warn', 'error']

What changed

  • quickjs-runner.ts — the ctx.log install loop covers four levels.
  • script-runner.tsScriptContext['log'] declares debug.
  • body-runner.ts — the host-side surface forwards debug to the factory's logger, and warns for it in the no-logger branch. This file is load-bearing for the fix, not incidental:installCtx forwards through the optional call ctx.log?.[level]?.(…), so a VM-side debug with no host-side member is not a throw but a silent drop — the [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 defect verbatim. (Noted for the file-surface declaration: the card named quickjs-runner.ts + script-runner.ts; body-runner.ts is the third file in the same runtime/src/sandbox/** directory the claim reserved, and the consequence cannot be delivered without it. Nothing outside that directory is touched.)

Two pre-existing test doubles built a three-member ctx.log; they now build four. packages/cli/src/utils/extract-hook-body.ts and the docs table are deliberately untouched — under the enforce arm both were already correct.

Reverse-verification — measured in both directions

The pin must fail for a missingdebug and for a no-opdebug. Both arms were run.

1. Install reverted (i.e. the pin against unmodified main) — red, with the card's own error:

FAIL src/sandbox/body-log-capability.test.ts > [#7661] … > delivers the card's own reproduction
Caused by: SandboxError: hook 'h' threw: TypeError: not a function
❯ QuickJSScriptRunner.execute src/sandbox/quickjs-runner.ts:400:17
FAIL … > gates debug behind the log capability like the other three levels
AssertionError: expected [Function] to throw error matching /capability 'log' not granted/
+ Received: "hook 'ungranted_hook' threw: TypeError: not a function"
Tests 4 failed | 5 passed (9)

2. VM install kept, host-side forward removed (the no-op arm) — still red, and nothing throws:

FAIL … > delivers the card's own reproduction — ctx.log.debug('hi') — as a debug record
AssertionError: expected +0 to be 1
FAIL … > carries structured data across the VM boundary as a value
AssertionError: expected undefined to be 'debug'
Tests 2 failed | 7 passed (9)

That is the point the card insisted on: a debug installed as a no-op passes a throw-only test, so every assertion is on what the host logger received, not on the absence of an exception.

3. After the fix — green:

 Test Files 1 passed (1)
Tests 9 passed (9)

Verification record

CommandResult
npx vitest run src/sandbox/body-log-capability.test.ts9 passed (4 new)
npx vitest run src/sandbox/12 files, 138 passed
npx vitest run (whole @objectstack/runtime)137 files, 2090 passed
pnpm --filter @objectstack/runtime... buildsuccess (DTS included)
pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*'70/70 successful
pnpm check:changeset-gate-self-tests✓ 118 + 153 + 117 assertions
node scripts/check-empty-changeset.mjs✓ 1 declaring changeset added
node scripts/check-changeset-no-major.mjs✓ no major bump
node scripts/check-nul-bytes.mjs✓ 7231 files scanned
pnpm check:type-check-debt✓ OK — 33 entries re-measured, none above its recorded number

Gate families derived with node scripts/pm/dispatch-gates.mjs <changed paths> (→ check:changeset-gate-self-tests), plus the convention-scoped gates and the affected package's suites.

On the type-check ratchet. The first run reported @objectstack/runtime TEST_DEBT 227 → 229 (+2). The two new errors were real and mine: making debug required on ScriptContext['log'] broke the two three-member test doubles (capability-denial-is-a-fault.test.ts:95, quickjs-runner.test.ts:114, both TS2741: Property 'debug' is missing). They were fixed, not ledgered — the entry is back to exactly its frozen 227 and the ceiling was not raised.


Generated by Claude Code

`ctx.log.debug` was declared on three surfaces and implemented on none: the
CLI capability extractor matched it and granted `log`, the docs table taught
it, and the QuickJS install loop covered only `info`/`warn`/`error`. A body
that followed the documentation therefore threw `TypeError: not a function`
inside the VM — and under `onError: 'abort'` that aborted the write.
Enforced rather than retired (ADR-0049). The `crypto.hash` precedent this
shape echoes (#4391) was removed because implementing it widened the sandbox's
security surface; emitting a debug-level diagnostic carries no such argument,
and `Logger.debug(message, meta)` already existed on the contract.
- `quickjs-runner.ts`: the install loop now covers four levels.
- `script-runner.ts`: `ScriptContext['log']` declares `debug`.
- `body-runner.ts`: the host-side log surface forwards `debug` to the
factory's logger, and warns for it in the no-logger branch. Without this
the new VM method would be a SILENT DROP rather than a throw, since
`installCtx` forwards through the optional call `ctx.log?.[level]?.(…)`.
Two existing test doubles built a three-member `ctx.log`; they now build four.
Reverse-verified in both directions: with the install reverted the new pin
fails with the card's own `TypeError: not a function`, and with only the
host-side forward removed it fails on the delivery assertions while nothing
throws — so the pin cannot be satisfied by a no-op `debug`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CUymLqPDq3d7PgiwpZsbA2
@vercel

vercelBot commented Aug 12, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 12, 2026 3:47am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/runtime.

20 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx(via packages/runtime)
  • content/docs/api/index.mdx(via @objectstack/runtime)
  • content/docs/api/wire-format.mdx(via @objectstack/runtime)
  • content/docs/automation/hook-bodies.mdx(via @objectstack/runtime)
  • content/docs/concepts/metadata-lifecycle.mdx(via @objectstack/runtime)
  • content/docs/concepts/north-star.mdx(via packages/runtime)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/runtime)
  • content/docs/deployment/index.mdx(via @objectstack/runtime)
  • content/docs/deployment/production-readiness.mdx(via @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx(via @objectstack/runtime)
  • content/docs/deployment/vercel.mdx(via @objectstack/runtime)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/runtime)
  • content/docs/kernel/cluster.mdx(via @objectstack/runtime)
  • content/docs/permissions/authentication.mdx(via @objectstack/runtime)
  • content/docs/permissions/authorization.mdx(via packages/runtime)
  • content/docs/permissions/system-context.mdx(via packages/runtime)
  • content/docs/plugins/packages.mdx(via @objectstack/runtime)
  • content/docs/protocol/kernel/http-protocol.mdx(via @objectstack/runtime)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/runtime)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/runtime)

2 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx(via @objectstack/runtime)
  • content/docs/releases/v17.mdx(via @objectstack/runtime)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

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

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] ctx.log.debug is inferred, granted and documented, but the sandbox never installs it — a body calling it throws TypeError: not a function

2 participants

@hotlong@claude