Repository navigation
Conversation
`sourceMap: true` on `bundle`/`bundleApp` returns `map` beside `module`: an esbuild map of the final compile (not the per-function probes) with no sourcesContent and no sourceRoot. The module keeps the trailing `//# sourceMappingURL=_bundled.mjs.map` comment Cloudflare reads to remap stack traces, and otherwise matches the option-off build byte for byte. The map's sources name the request's files: per-app `fn_<index>/` is stripped and checked against that function's files, and a function's entry becomes its optional `entryPath` (its real stored path). Every other source stays in place, so the mappings' indices hold, behind a `base44-internal:` prefix. Off by default; the public CLI never sets it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
egidijusj
force-pushed
the
feat/functions-compiler-error-records
branch
from
October 20, 2026 06:42
f570f53 to
9d49b63
Compare
…e_map Cloudflare remaps only the stacks of uncaught exceptions it captures; a stack the Worker logs itself (the per-app entry's console.error before rethrowing, or user logs) stays minified. Run a throwing function in workerd with a Tail Worker, for both entries, and map the exception's frames back to the app's files by hand — local workerd does no remapping — and pin that the logged per-app crash line stays minified. Say in the README what is remapped automatically and what a consumer must remap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`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>
egidijusj
force-pushed
the
feat/functions-compiler-error-records
branch
from
October 20, 2026 07:23
9d49b63 to
d4cfc86
Compare
…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>
egidijusj
marked this pull request as ready for review
October 20, 2026 07:45
yurynix
previously approved these changes
Oct 20, 2026
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>
egidijusj
force-pushed
the
feat/functions-compiler-error-records
branch
from
October 20, 2026 08:38
b2dd271 to
de77dbf
Compare
_b44_error record for a returned 5xx_b44_error line
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Every 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
_b44_errorline saying what went wrong, so the Production Agent's Tail Worker has a reason to file, not just a status.There is no build option. The off switch is the request: the recorder does nothing unless the request carries
x-b44-feed-context. The backend sends that header only for production calls of apps that opted in, while its tail switch is on. Turning that switch off silences the record in every bundle, whatever built it.Why it's in every build
COMPILE_MODES(packages/cli/src/core/version/functions.ts), which would never have set a flag.headers.hasper request and one context lookup perconsole.*call. Source maps (feat(functions-compiler): opt-in external source maps with app paths #668) keep their flag, because they cost compile time and memory and are useless unless the caller stores them.Request and response changes
None. An earlier revision of this PR added an
errorRecordsoption; it's gone. Nothing shipped with it, so no caller sends it and no compatibility shim is needed. The build banner is unchanged frommain: theerrorRecordsfield is gone, and the banner'scompilerversion tells a consumer whether a build carries the record.The line, logged through the console patch (so per-app scripts stamp
_b44_function):Behaviour (
src/error-records.ts), unchanged by this revisionInert unless the request carries
x-b44-feed-context. Presence is enough; the value isn't checked.Handler
Responsewith status ≥ 500: the same object is returned at once, never rebuilt, delayed or given a header. The recorder clones it and does the rest insidectx.waitUntil. It sees the handler's own response, before telemetry or invocation logs re-wrap it.Message, in order:
console.*) during this request whose message the JSON body repeats anywhere. The recorder keeps the last 8 Errors logged in the request and uses the newest one that matches, so a later unrelated warning doesn't hide it;error,message,detail,details,error.message;erroris skipped whendetailsis present anderroris exactly"Internal server error";No message means no record.
Stack only from the logged Error whose message was chosen. It's minified: it's that Error's raw
stack, read inside the Worker, and Cloudflare remaps only the exceptions it captures. The Production Agent remaps it with the same version'ssource_mapfrom feat(functions-compiler): opt-in external source maps with app paths #668.Scrub before cutting. Every string of 8+ characters from the request's env bindings and runtime secrets, longest first, becomes
[redacted]in the name, message and stack. This includes strings nested in JSON-valued bindings, also when they start with whitespace (such as the private-data-sources manifest, which carries DB passwords). Binding objects (KV, services, Hyperdrive) are skipped.Bounds:
name≤ 64 characters;message≤ 500 UTF-8 bytes aftertoWellFormed(), cut on a code point;stack≤ 4 KB, cut at a line end.Never throws into the request. Any failure writes nothing.
Crash path untouched. The per-app entry's catch still does
console.error(e)thenthrow e. A test pins that block's exact text.This changes every build's output once
Snapshot change, in one line: each of the four pinned
worker-entrysnapshots (single-function and per-app, telemetry on and off) gains the recorder prelude. In each, exactly two existing lines change: the request store gains..._b44ErrorStore(request), and the return becomes_b44RecordError(await handler(...)).So every app's next build produces different bytes. Where a version's identity is the hash of the compiled output, as in the CLI's versions path, that mints one new version per app on its next publish. A package-version bump does the same anyway.
Byte comparison of the single-function lane
The README said
bundle()stays bannerless "which keeps that lane byte-comparable with the engine apper still runs". I found nothing that still compares it.infra/base44-userapp-bundler) pins and runs this package (0.1.7), so there's no second engine to compare against.function_bundle.py) are about the bundle input, the flatmain.tssubmission, not the compiled output.bundle-banner.e2e.test.tsonly asserts thatbundle()has no banner, which still holds.I kept
bundle()bannerless, since it compiles one function and has nothing to name, and reworded the README. The caveat: this is from a local apper checkout I didn't update, so a newer apper commit could differ. No flag was kept for it.Open question: client-sent
x-b44-feed-contextThe recorder treats the header's presence as the switch. Now that every app carries it, it matters whether the dispatcher strips a client-supplied
x-b44-feed-contextbefore calling a function. I couldn't find the header anywhere in the local apper checkout, so I can't verify this. The impact of a spoofed header is small: it only records a 5xx the function really returned, with secrets scrubbed, into that app's own logs. Please confirm the dispatcher drops it from client requests.Tests
test/error-records.e2e.test.tsruns every case in workerd (Miniflare) and reads records through a real Tail Worker, the way production reads them. With no build option, the header is the only switch: every positive case also asserts there's no record without the header, and the cases that compared an option-off build now compare header-absent vs header-present on the same build.error,message,detail,details,error.message, first non-empty wins. Both SSO shapes:{error:"Internal server error", details}givesdetails, and{error:"Internal server error"}gives the placeholder. A near-miss isn't skipped. A 503 reports 503. The per-app entry works too and stamps_b44_function;Responsereturn, a body over 2 KB, and a streamed body that misses the 50 ms deadline. The client still gets the whole body in the last two;Responsesubclass whoseclone()throws): the response stands and no record is written;exceptionswith and without the header, and no record.test/worker-entry.test.ts:functions, telemetry, runtimeSecrets, compiler;Results:
typecheckacross all packages: clean;lintandknip: clean;tests/core/version-functions.spec.ts, its function-compiling publish path: 4/4.Notes for review
CHANGELOG.mdis the CLI's. The release notes should say: every build now carries the_b44_errorrecord, inert until the backend sendsx-b44-feed-context. The README says the same.X-B44-Capture-Logsturns an uncaught throw into, which is the crash path;worker-entry.tsand its snapshots, so whichever lands second needs a rebase and a snapshot regeneration.dispatchFetchhands a streamed body to the caller only once it closes, so the test times the headers.🤖 Generated with Claude Code