ADR-0003: Fix swallowed startup error logging - #2
Open
nicholas-ruest wants to merge 1 commit into
Open
Conversation
Pino applies serializers by property name. The logger registered
pino.stdSerializers.err under the key `err`, but this codebase logs caught
values under `error` at 62 call sites (and `reason` for unhandled rejections).
Error.prototype.message/.stack/.name are non-enumerable, so those Errors fell
through to default JSON serialization and rendered as `{}` — including every
startup-hardening path. Cloud Run revision ruvvector-service-00052-dxg
crash-looped with `{"error":{}}` as its entire diagnostic payload.
Register `error` and `reason` alongside `err` rather than editing 62 call
sites. pino 9's `errorKey` option configures exactly this, but this service is
on pino 8.21.0 and an incident fix should not carry a major version bump.
Separately, RUVVECTOR_EMBEDDING_DIM hard-crashes assertEmbeddingDimension()
when unset but was absent from REQUIRED_ENV_VARS_PRODUCTION, so the first
assertion reported "missing":0 while the service was already doomed. Adding it
makes that count mean what it says. The variable keeps no default — the value
fixes vectors.embedding's width and cannot be changed without rewriting the
table, so it must be supplied explicitly at deploy time, not guessed in code.
Verified by rebuilding and running with the variable unset: before, the fatal
record is `{"error":{}}` after `"present":5,"missing":0`; after, it names
RUVVECTOR_EMBEDDING_DIM with a full type/message/stack.
Implements ADR-0003: fix swallowed startup error logging
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.
Implements ADR-0003. Source changes only — this PR deploys nothing and sets no environment variable on any service.
What was broken
Cloud Run revision
ruvvector-service-00052-dxgcrash-looped on startup and the entire diagnostic payload was{"error":{}}.(a) Every error log in the repo was empty. Pino applies serializers by property name.
src/utils/logger.tsregisteredpino.stdSerializers.errunder the keyerr, but this codebase logs caught values undererror— at 62 call sites, including every startup-hardening path (healthCheck.ts,DatabaseClient.ts,immutability.ts, and the top-level catch atindex.ts:635).Error.prototype.message/.stack/.nameare non-enumerable, soJSON.stringify(new Error('boom')) === '{}'. The service was undebuggable at startup — precisely where it is designed to crash hard.(b) The actual crash was an unset
RUVVECTOR_EMBEDDING_DIM, and the assertion whose job is to report missing required variables was structurally incapable of catching it:REQUIRED_ENV_VARS_PRODUCTIONhad five entries and the dimension was not among them. It logged"missing":0while the service was already doomed two assertions later.What changed
src/utils/logger.tserrorandreasonalongsideerrin the serializer mapsrc/startup/assertions.tsRUVVECTOR_EMBEDDING_DIMtoREQUIRED_ENV_VARS_PRODUCTIONtests/unit/logger-serialization.test.tsdocs/adr/ADR-0003-*.mdOne serializer entry fixes all 62 call sites atomically, with no chance of missing one.
stdSerializers.erremitstype/message/stack, walkscausechains, and passes non-Error values through unchanged. The existingerrkey is retained and still works.reasongets the same treatment rather than editingindex.ts:614, so unhandled rejections are legible too. It is used as a log key in exactly one place, so the map entry is not overbroad.Rejected: pino's
errorKey: 'error'option, which configures exactly this behavior but requires pino 9.0. This service is on 8.21.0; an incident fix should not carry a major version bump.Real test output
npm test— 130 passed, 8 failed, 138 total.The 8 failures are pre-existing on
mainand unrelated. Verified by stashing this branch's changes and running the same two suites at1813a25: identical8 failed, 34 passedinexecutions.test.tsandsimulations.test.ts. This branch is exactly +7 passing tests.New regression suite:
The last case pins the defect itself: with only
errregistered,{ error: new Error('boom') }still serializes to{}. That test fails if someone removes theerrorentry.npm run type-checkclean.npx eslint src/utils/logger.ts src/startup/assertions.ts— 0 problems (the repo's 5 pre-existing lint errors are in other files).End-to-end reproduction of the production crash
Built image run locally with every required variable set except
RUVVECTOR_EMBEDDING_DIM— the exact environment of00052-dxg.Before (clean
main) — reproduces the incident byte-for-byte, including the misleading"missing":0:After (this branch):
This confirms ADR-0003's root-cause hypothesis and closes its residual ~3% uncertainty (that
assertValidDimension()was rejecting a malformed value instead). BothassertEmbeddingDimension()paths were also verified to log real errors now:RUVVECTOR_EMBEDDING_DIMThis PR does NOT set
RUVVECTOR_EMBEDDING_DIManywhere. It is not in the code, not in a default, not in a config file, and not on any Cloud Run service. Merging this PR alone will not make the service start — it will make it tell you clearly why it didn't.The variable must be set as a real deploy-time environment variable by a human. It deliberately has no default in code and must not be given one.
Candidate value:
1536— requires explicit human confirmation before use.1536is the only candidate value evidenced anywhere in the workspace, and its single source iscopilot-agent/.env.example:114(EMBEDDING_DIMENSIONS=1536).copilot-agentandplatform-agents/src/index.tsactually POST to/v1/vectors/store.vector(N)fixes N in the table definition.VectorClient.connect()(src/clients/VectorClient.ts:151-157) refuses to start on any mismatch against the live column, and correcting a wrong choice after rows exist means a table rewrite plus a full backfill.Because of that, 1536 must not be silently defaulted in code. Confirm the producer's true dimension first, then set it explicitly at deploy time:
Note
scripts/deploy-cloudrun.sh:18still contains a plaintext database password (ADR-0002's scope, not fixed here) — remediate that before adopting the script as the standard deploy route.Scope / safety
00051-gwpis untouched and still serving 100% of traffic.startServer()have never executed against this Cloud SQL instance with real persistence. That would be the logging fix working, not a regression.{}used to be. Intended trade. Audited startup messages interpolate config but not credentials (EXECUTION_HMAC_SECRETis only ever logged by length) — worth re-checking whenever a newthrowinterpolates config.Remaining ADR steps (open, not in this PR)
--no-traffic, promote only once healthy