Skip to content

feat: error tracking for api, admin and app (CLEAN-218) - #182

Merged
maksymhryzodub-prog merged 10 commits into
mainfrom
feat/CLEAN-218-error-tracking
Nov 11, 2026
Merged

maksymhryzodub-prog merged 10 commits into
mainfrom
feat/CLEAN-218-error-tracking

Conversation

@maksymhryzodub-prog

@maksymhryzodub-prog maksymhryzodub-prog commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Ticket: CLEAN-218

A failure in the API or in a console reaches nobody today: it stays in the pod log and in the user's browser. This reports unexpected failures from api, admin and app to our GlitchTip, each to its own project, with main-site's rule set.

It ships switched off. No destination is in the repository. Nothing reports until three DSNs are put into a cluster secret, which is a separate step after the release. The Mazda fork gets this code through a catch-up merge and stays silent.

What is in

  • One rule set. sentry.rules.ts decides what is reported, labels it and strips private data. It exists three times, byte for byte, in api, admin and app, and imports nothing. Every event passes through it in beforeSend, however it was captured.
  • API. A global exception filter reports a failed request and then answers exactly as Nest would have. The two process handlers in main.ts report beside their log line. Twelve catch blocks that log and carry on now report too.
  • Consoles. One call at the end of handleError. The SDK is downloaded only when a production build has a DSN at runtime.
  • Rules. 401 and every other 4xx stay out; 5xx and unknown errors go in with statusCode and errorCode. Also out: the user's own network, a stale tab asking for a removed chunk, the Kubernetes watch timeout main.ts already swallows, and everything in local development.
  • Privacy. No request or response bodies, no cookies; tokens, keys and emails are filtered, in query strings too; the user is an opaque id; the operation is the route pattern.
  • Config. One optional secret, ranch-error-tracking, with a key per project, referenced from all five manifests. docs/operations/error-tracking.md is the operator's page.

Two things a reviewer should know

  1. The API's "deployed" gate is not NODE_ENV. k8s/deploy/30-api.yaml runs the production API with NODE_ENV: "dev". The gate is "runs from a built image": RANCH_VERSION set and not dev.
  2. ErrorMapper changed in both consoles. A request that got no answer has no status and was mapped to 500; it now keeps the client's ERR_NETWORK / ERR_CANCELED code, so a dropped connection is not reported as a server failure. Status, message and toast are as before.

Twin consoles

Both consoles were changed, identically: the new setup/sentry slice is the same directory in admin and app, and the edits to setup/error (error.service.ts, error.mapper.ts) are the same in both. scripts/sentry-twin.test.ts now fails CI if the slices or the three rules files drift, or if a real DSN appears anywhere in the repository.

Background sites wired

chat.insight, chat.sync, reins.index-reconcile, reins.extraction (2), reins.migration (2), reins.provision, reins.indexing, agent.deploy, reins.archive-import, paddock.evaluation, integration.extension-archive. agentEvent.ingest.controller keeps the original error as the cause of its 500.

Left alone, with the reason, in specs/029-error-tracking/research.md R6: sites that rethrow, MCP handlers that answer the caller, the SSE ping to a closed connection, and the two per-knowledge retries in indexReconcile, which run on a 60-second tick and would send an event a minute per knowledge base while LightRAG is down.

Gates run

Command Result
cd api && npx jest 176 suites, 2498 tests pass
cd api && bun run build clean
eslint on every API file touched 0 errors
bun run test:scripts 99 pass
npx nuxt typecheck in admin and in app exit 0 both
bun test slices in admin / app 280 / 233 pass
bun run locale:check, bun run i18n:check no findings

Checked by running it, against a local HTTP server standing in for the tracker (our GlitchTip was not touched):

  • API adapter under Bun with NODE_ENV=dev: three of six captures arrive, a 404, a 401 and the watch timeout do not; no session envelope; with the tracker unreachable nothing throws and the calls return in 5 ms.
  • admin in a browser, production build with a DSN set at runtime: a component error, a 503 and an uncaught timer error arrive; a 400, an ERR_NETWORK, a rejected 401 and a stale chunk do not; ?token= in the address arrives as [Filtered].
  • admin production build without a DSN, and nuxt dev with one: nothing sent, SDK not downloaded.

Not in this PR

  • Readable console traces (source maps, tasks T042-T046). Needs an upload token for the tracker and a check that our GlitchTip resolves the upload.
  • Reading raw events from real flows (T031). The scrubbing is covered by the spec on Ranch-shaped events; forcing failures through sign-in, chat, upload and credentials needs a running Ranch and is part of the rollout.
  • app was not opened in a browser; its slice is byte-identical to admin's and its typecheck and tests pass.
  • The API was not booted with a database here; the filter is tested through Nest's testing module.

Failures in the API and in both consoles reach nobody today: they stay in the pod log and in the user's browser console. The spec asks for them to be reported to the team's GlitchTip, one project each, with main-site's rule set: expected errors stay out, server failures and unknown errors go in labelled with status and error code, private data is removed first. Only the Ranch production reports; the Mazda fork inherits the code and stays silent because no destination is committed. The plan, the two contracts and the research record how one rule set is shared by three projects that cannot import from each other, and what was verified against a local stand-in for the tracker.
@sentry/bun for the API, which runs under Bun in production, and @sentry/vue for the two consoles, which are SPAs. The 10.x line, the one main-site already runs against the same GlitchTip; 11 is out but unproven there. The lockfile was written by Bun 1.4.4, the version the Dockerfiles pin, and only adds entries.
A setup/sentry slice. sentry.rules.ts decides what is reported, labels it and strips private data; it imports nothing, so the same file serves the consoles. Every event passes through it in beforeSend, however it was captured. A global exception filter reports a failed request and then lets Nest answer exactly as before; a filter rather than an interceptor because guards and pipes throw before any interceptor runs. The error-handling interceptor keeps the original error as the cause of the 500 it builds, so the tracker gets the real stack. The two process handlers in main.ts report beside their existing log line, and twelve catch blocks that log and carry on now report too, deploy and the knowledge jobs among them.

Off unless the process runs from a built image (RANCH_VERSION baked in, not NODE_ENV: the live API runs with NODE_ENV=dev) and SENTRY_DSN is set; otherwise the SDK is not even loaded. The SDK's own uncaught-exception handlers are left out because the first one exits the process, and the Kubernetes watch timeout that main.ts already swallows is ignored by rule. Request bodies and cookies are never attached, the user is an opaque id, and the operation is the route pattern, never the URL.
…218)

The same setup/sentry slice in both consoles, file for file, built around the same sentry.rules.ts the API carries. handleError gains one call at its end, where main-site has it, so the toast, the redirect to sign-in and the dev logging are untouched; errors the browser raises by itself take the same rules in beforeSend. Events carry the screen as a route pattern and the signed-in user as an id, cleared on sign-out.

The SDK is downloaded only when a production build has NUXT_PUBLIC_SENTRY_DSN set at runtime; nuxt dev and a build without it never fetch it. ErrorMapper keeps the client's ERR_NETWORK or ERR_CANCELED code on a request that got no answer: such a request has no status and was mapped to 500, which would have reported every dropped connection as a server failure.

scripts/sentry-twin.test.ts fails CI when the three copies of the rules differ, when the two console slices differ, or when a real DSN appears anywhere in the repository. Twin check: both consoles were changed identically, in the sentry slice and in the two setup/error files.
…(CLEAN-218)

The api, admin and app deployments read their DSN from a key of the ranch-error-tracking secret, referenced as optional in all five manifests. The values live in the cluster and never in this repository, so a cluster without the secret starts as before and reports nothing, and a fork that merges these manifests does not start reporting to our tracker.
An operations page for whoever turns reporting on for an installation: what is and is not reported, what never leaves, the one secret and its three keys, how to check it works and how to turn it off. CLAUDE.md gains the rule that sentry.rules.ts is edited in the API and copied to both consoles in the same commit, and that a DSN is never written into a file, a ticket or a PR.
…EAN-218)

The rules filtered headers, addresses, extra and contexts but sent an exception's own message as written, and in Ranch that text can quote what the failing code held: a database URL with its password, a share link with its token, an upstream answer echoing a Bearer header, a person's email. scrubText now runs over the event message, every exception in the chain and breadcrumb messages, and masks URL user-info, sensitive query parameters, Bearer and Basic values, key=value credentials, sk- provider keys and emails. Ordinary messages come through unchanged. Raised by the automated security review of the first push; the three copies of sentry.rules.ts change together.
…turned on (CLEAN-218)

The release workflow restarts the deployments but does not apply manifests. ArgoCD syncs k8s/platform from main; k8s/deploy is applied by hand. The operations page now says so, and how to check that a deployment carries the reference.
…off (CLEAN-218)

Two holes in the text masking added one commit ago. The user-info of a URL was masked up to the first /, #, ? or @, so a password holding one of those kept its tail; it is now everything up to the last @. And the SDK cuts a long message and appends ... before the rules see it, so a database URL cut before its @ went out with half a password; a cut-off tail that looks like an address or an assignment is now masked whole. Raised by the automated security review. The three copies of sentry.rules.ts change together.
The pattern that masked a cut-off tail was unanchored over runs of non-space characters, so a long message with no spaces made it backtrack, and it runs on the request path. The tail is now found by walking back from the end, and any text is cut to 2048 characters before a pattern touches it. Raised by the automated security review. The three copies of sentry.rules.ts change together.
@maksymhryzodub-prog
maksymhryzodub-prog merged commit 9df2676 into main Nov 11, 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