Repository navigation
Conversation
`errorRecords: true` on `bundle`/`bundleApp` bakes a recorder into both
generated entries. It is inert unless the request carries
`x-b44-feed-context`. For a handler `Response` with status >= 500 it
returns that same response at once and, on a clone inside
ctx.waitUntil, logs one
`{ _b44_error: { v: 1, kind: "response", status, name?, message, stack? } }`
through the console patch.
The message comes from an Error logged during the request whose message
the JSON body repeats, else the body's first non-empty error / message /
detail / details / error.message (2 KB, 50 ms; the SSO
"Internal server error" placeholder skipped when details is present),
else the last logged Error. The stack comes only from that Error. Env and
runtime secrets of 8+ characters are scrubbed, longest first, before
name (64 chars), message (500 bytes) and stack (4 KB) are cut. Any
failure writes nothing; the crash path is unchanged.
Off by default; the public CLI never sets it, and flag-off entries and
banners keep their bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It is the raw Error.stack read inside the Worker, and Cloudflare remaps only the exceptions it captures, never a logged string. Say so, and that a consumer remaps it with the same version's source_map. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y recent logged Error
Two review findings on the error recorder:
- A JSON-valued binding with leading whitespace (`\n{"db":{...}}`) was
not parsed, so the secrets nested in it were logged unredacted.
Detect JSON after trimStart(); the original string stays a candidate.
- A single saved Error was overwritten on every log, so a later
unrelated warning lost the matching Error's name and stack. Keep the
last 8 Errors logged in the request, use the newest one whose message
the body repeats, and the newest one only as the fallback.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The error recorder now replaces a matched secret with `[redacted]` instead of `[secret]`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drop the `errorRecords` option. Both generated entries now always append the recorder, route the handler's response through `_b44RecordError` and seed `_b44ErrorStore(request)`, including the public CLI's own shard builds and bundleApp's probe compiles. An app is ready for error reporting the moment it opts in, with no rebuild. The off switch is the request: the recorder does nothing unless it carries `x-b44-feed-context`, which the backend sends only for production calls of opted-in apps while its tail switch is on. Runtime behaviour is unchanged, crash path included. The banner loses its `errorRecords` field; `compiler` says which builds carry the record. This changes every build's output once: about +3 KB raw (+1.2 KB gzipped) per module. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cord
The record is logged through the console patch, not raw: a Tail Worker
gets one entry with level "log" whose single argument is the patch's
wrapper `{ _b44_env, _b44_function?, level: "error", message }`, and
`message` is the JSON string of `{ "_b44_error": {...} }`. apper's error
tail is the consumer. Pin that entry for both entries, so a change to
the console patch fails here instead of silently in production, and
state the shape in the README and the module doc. No change to what is
emitted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s-compiler-error-records
🚀 Package Preview Available!Install this PR's preview build with npm: npm i @base44-preview/cli@0.2.2-pr.678.b8808dcPrefer not to change any import paths? Install using npm alias so your code still imports npm i "base44@npm:@base44-preview/cli@0.2.2-pr.678.b8808dc"Or add it to your {
"dependencies": {
"base44": "npm:@base44-preview/cli@0.2.2-pr.678.b8808dc"
}
}
Preview published to npm registry — try new features instantly! |
DescriptionEvery build of a backend function now carries a small recorder in its generated Worker entry. When a function catches its own error and returns a 5xx, the recorder logs one There is no build option. The off switch is the request: the recorder does nothing unless the request carries
Why it's in every build
Request and response changesNone. An earlier revision of this PR added an What a consumer sees (the contract)The recorder calls
A consumer keeps entries whose wrapper Invocation logs. The record is logged inside
Confirmed in workerd. It is still an ordinary stamped line, so views built on stamped Workers log lines (such as per-function log queries) show it as an error line whose message is the JSON. That applies only to production calls of opted-in apps, with secrets scrubbed. Behaviour (
|
Note
Description
A backend function that catches its own error and returns a 5xx leaves Cloudflare nothing to record — the Tail Worker sees a status and no reason. This PR adds a small, self-contained recorder to every generated Worker entry (single-function and per-app; not actors) that logs exactly one
_b44_errorline for a handled 5xx, carrying the message, an optional error name and a minified stack, with secrets scrubbed. There is no build option: the off switch is the request, so the recorder is inert unless the call carriesx-b44-feed-context, which the backend sends only for production calls of opted-in apps. An app is therefore ready the moment it opts in, with no rebuild — including apps compiled by the public CLI’s ownCOMPILE_MODESpath.Related Issue
None. Stacked on #677 (source maps) — this PR’s base is
feat/functions-compiler-source-maps, so the diff shows only its own changes; GitHub retargets it tomainwhen #677 merges. Replaces #669 (opened from a fork, where CI’s secret-dependent jobs cannot run); the earlier review discussion is there.Type of Change
Changes Made
packages/functions-compiler/src/error-records.ts—ERROR_RECORDS_PATCH, a dependency-free JS prelude inlined into the generated entry after the console patch. It provides_b44ErrorStore(a per-request store created only when the header is present), aconsole.*wrapper that remembers the last 8 loggedErrors, and_b44RecordError(res), which returnsresuntouched and does all its work on a clone insidectx.waitUntil.error,message,detail,details,error.message, skippingerrorwhen it is exactly"Internal server error"anddetailsis present) → the newest logged Error. No message means no record.[redacted], longest first, before anything is cut.name≤ 64 chars,message≤ 500 UTF-8 bytes (toWellFormed(), cut on a code point),stack≤ 4 KB (cut at a line end). The stack is minified by design; a consumer remaps it with the version’ssource_mapfrom feat(functions-compiler): opt-in external source maps with app paths #677.worker-entry.ts: bothbuildEntrySourceandbuildAppEntrySourcenow inlineERROR_RECORDS_PATCH, spread..._b44ErrorStore(request)into the request store, and route the handler through a sharedHANDLER_CALL(_b44RecordError(await handler(request, info))) so the recorder sees the handler’s own response, inside telemetry and invocation logs. No new build flag and no banner field — the banner keys stayfunctions, telemetry, runtimeSecrets, compiler.test/error-records.e2e.test.ts(550 lines) running every case in real workerd via Miniflare and reading records through a real Tail Worker;test/worker-entry.test.tsgains a recorder suite; the four pinned entry snapshots are regenerated (each gains the prelude plus two changed lines).Testing
npm test)Checklist
docs/(AGENTS.md) if I made architectural changesAdditional Notes
What a consumer actually sees. The recorder calls
console.error({ _b44_error: record }), which goes through the console patch — so a Tail Worker does not see that object. It gets one log entry whoselevelis"log"and whosemessageis a single argument, the patch’s wrapper:{ _b44_env, _b44_function?, level: "error", message: "<JSON string>" }, wheremessageis the JSON of{ "_b44_error": { v: 1, kind: "response", status, name?, message, stack? } }. A consumer filters on the wrapper’slevel === "error"and readsJSON.parse(wrapper.message)._b44_error. apper’s error tail (workers/base44-log-tail/src/error-tail.ts) parses the raw object today, so it drops every record until it is updated — harmless to merge before that; records are simply dropped.Size and release impact. About +3.0 KB raw / +1.2 KB gzipped per module (single-function 41,400 → 44,422; per-app 43,159 → 46,191). Without the header the cost is one
headers.hasper request and one context lookup perconsole.*call. Every function’s bytes change once on its next deploy after apper’s bundler picks up this version; where a version’s identity is the hash of the compiled output (the CLI’s versions path), that mints one new version per app on its next publish — which a package-version bump would do anyway.Invocation logs are unaffected: the line is logged inside
waitUntil, after the response has gone back, so it never lands in theX-B44-Invocation-Logsheader. It is still an ordinary stamped line, so per-function log views show it as an error line.No record is written for: responses the wrapper builds rather than the handler (the per-app "failed to initialize" 500, the 503/404 routing answers), the per-app crash response, and actor entries.
Open question for review: the recorder treats the header’s presence as the switch. Please confirm the dispatcher strips a client-supplied
x-b44-feed-contextbefore invoking a function. The impact of a spoof is small — it only records a 5xx the function really returned, scrubbed, into that app’s own logs.Conflict note: #662 (edge errors on outbound fetches) also edits
worker-entry.tsand its snapshots; whichever lands second needs a rebase and a snapshot regeneration.Version/changelog: deliberately none — the Manual Functions Compiler Publish workflow bumps this package’s version in its own release commit, and the root
CHANGELOG.mdis the CLI’s.Reported results (from the author’s run; not re-executed while generating this description): full compiler suite 490/490;
typecheck,lintandknipclean; the CLI’stests/core/version-functions.spec.ts4/4.🤖 Generated by Claude | 2026-10-23 15:09 UTC | b8808dc