Skip to content

fix(api): phase i.3, sentry scrub, ordered shutdown and cron heartbeat - #76

Merged
axelhamil merged 4 commits into
devfrom
refactor/phase-i-3-ops
Oct 29, 2026
Merged

axelhamil merged 4 commits into
devfrom
refactor/phase-i-3-ops

Conversation

@axelhamil

Copy link
Copy Markdown
Owner
  • @packages/sentry-scrub shared by api and app: strips text after params:, query strings and emails from messages, exceptions, contexts, extra, breadcrumbs and pino logs (beforeSendLog); the app now also clears cookies
  • URL scrub regex rewritten: a 10 KB path went from 55 s to 0.2 ms (ReDoS)
  • IInstrumentation.flush; hand fakes of the port replaced by mockInstrumentation()
  • Ordered shutdown under a global deadline: grace, HTTP drain (then force close), workers in parallel with their own budget, both pg pools, telemetry flush; pool close skipped if workers timed out
  • env-bounds checks the exact worst case; new defaults: step 2 s, workers 12 s, deadline 36 s
  • Railway: draining time must be set above the deadline (doc), its default is only a few seconds
  • Cron sweep pings SWEEP_HEARTBEAT_URL (/start, base, /fail), never logs the URL
  • One raceWithTimeout helper replaces 4 copies; SIGTERM proof: steps in order, exit 0

A failed Drizzle query puts its bound parameters after "\nparams:" in
the error text, so emails and tokens reached Sentry through
exception.values and message, and console or fetch breadcrumbs carried
full query strings. The scrub moves into a pure source-only package,
@packages/sentry-scrub, used by both the api and the app: the app's
inline copy did not clear request cookies, it now shares the same
function and its tests.

One text cleaner cuts failed-query params, drops a query string or
fragment that follows a path or carries key=value, and masks emails.
Its patterns anchor on token starts, so a long run of slashes costs one
linear pass instead of the cubic backtracking of the first regex. It
runs on message and exception values, on every string nested in
contexts and extra (a TanStack queryKey lands there), on breadcrumb
messages and data, and through scrubLog on the pino lines that
pinoIntegration ships as Sentry logs, which bypass beforeSend.
IInstrumentation gains flush(timeoutMs), which resolves the SDK boolean
so the shutdown can log a flush cut short by its timeout. Sentry
delegates to Sentry.flush, NoOp resolves true. The hand fakes of the
port in the test suite move to mockInstrumentation() instead of each
growing a flush stub.
The shutdown becomes a list of named steps that runShutdown runs in
series. First the signal and grace, then http (an explicit Bun.serve
handle, kept on globalThis so bun --hot reloads it) drained with stop()
and cut with stop(true) just before its budget, since SSE streams never
end on their own. Then the four workers stopped in parallel as one step
under SHUTDOWN_WORKERS_TIMEOUT_MS, both pg pools closed, and last a
telemetry flush. The pool step is skipped when the workers timed out:
closing it under a running drain leaves a sent but unmarked email that
goes out twice. A failed or timed out step is logged and the next one
still runs, and the exit code is 0 only when every step finished.

SHUTDOWN_DEADLINE_MS caps the whole run and exits 1 after a last flush;
a second signal exits 1 at once. The boot rejects a deadline below the
real worst case, margins included. The setTimeout race used by the
shutdown, the health checks and the disposable email DNS lookup becomes
one raceWithTimeout helper that always clears its timer.
The bundled sweep cron now pings SWEEP_HEARTBEAT_URL (healthchecks.io
style) with /start before the first sweep, the base url on success and
/fail on any failure, including the early exit on an unreachable API,
so a run that never starts or never ends raises an alert. HttpHeartbeat
never throws, gives up after 10 s and reports only the error name or the
status, never the url that carries the check secret.
@axelhamil
axelhamil merged commit 68ceacd into dev Oct 29, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant