Skip to content

fix(observability): attach the real cause at three error-swallowing sites - #6336

Merged
waleedlatif1 merged 1 commit into
stagingfrom
fix/error-cause-observability
Aug 6, 2026
Merged

fix(observability): attach the real cause at three error-swallowing sites#6336
waleedlatif1 merged 1 commit into
stagingfrom
fix/error-cause-observability

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Problem

Three log sites discard the underlying error. All three were hit in production and each independently blocked root-cause analysis.

Sites

1. lib/logs/execution/trace-secret-projection.ts — four bare catch {} blocks fell back to structural-only spans with no reason attached, across ~30 distinct throw sites (node/depth/array limits, cyclic content, non-plain prototypes, accessor properties, ref-fetch failures, budget exceeded). Now each reports which invariant fired.

Security: every TraceSecretProjectionError message is a compile-time literal (the ${label} interpolations are all static constants), so logging it is safe. Any other failure originates outside the module and could quote trace content — a SyntaxError from JSON.parse embeds the text it choked on — so those are reported by name only. describeProjectionFailure enforces the split.

structuralOnlySpan still strips errorMessage, deliberately. It is a block's error text and routinely embeds resolved secret values (a provider error echoing a URL or header). The structural fallback exists precisely because projection/matching failed, so no redaction can be trusted on that path — retaining it would leak plaintext exactly when redaction is known broken.

2. ExecutionLogger"Failed to record execution usage to usage_log ledger; charge may be unbilled" logged "error":{}. Error.message/stack are non-enumerable, so Object.assign(entry, { error }) + JSON.stringify in the production logger yields {}. Now logs describeError(error).

3. WorkspaceFileStorage / FetchExternalUrl — same saveError:{} problem, plus uploadWorkspaceFile rethrew new Error(...)without a cause, so Drizzle's Failed query: wrapper — and with it the Postgres SQLSTATE — was unrecoverable. All 137 prod occurrences were the same select ... from "workspace" where id = $1 limit $2 for update in lib/billing/storage/tracking.ts. The rethrow now chains { cause: error } and both sites log describeError, which reports the deepest link's code. If the leading hypothesis (SELECT ... FOR UPDATE routed to a read replica) is right, the next occurrence logs SQLSTATE 25006 and settles it. That hypothesis is not proven here — this change only makes it observable.

What is now logged

Error names, messages, and driver codes/SQLSTATE. No user data, no secret plaintext, no query parameter values. describeError additionally strips the \nparams: <values> tail that DrizzleQueryError appends to its message, closing a pre-existing leak for every existing caller.

Tests

7 new tests, one per behavior; all verified failing with the source changes stashed.

@vercel

vercelBot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
docsReadyReadyPreviewAug 6, 2026 7:25pm

Request Review

@cursor

cursorBot commented Aug 6, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes error logging and cause chaining on billing ledger writes, workspace uploads, and secret projection fallbacks—observability-critical paths with deliberate redaction rules, but no intentional change to success paths.

Overview
Improves observability where swallowed errors previously logged as empty {} because raw Error objects don't serialize cleanly, and adds safer diagnostics for trace secret projection fallbacks.

describeError now strips Drizzle-style \nparams: <values> tails from every message in the cause chain so bound SQL parameters never reach logs; existing callers benefit without changing call sites.

Execution usage ledger failures log cause: describeError(error) (driver message, SQLSTATE) instead of a blank error object. Workspace file upload rethrows with { cause: error } so the Postgres driver code survives wrappers, and fetch-external-url / workspace-file-manager warn/error paths use the same structured cause field.

Trace secret projection attaches a failure payload on warn logs when content is omitted or structural fallbacks run: fixed invariant messages from TraceSecretProjectionError are logged as reason; failures from outside the module log name only so trace content isn't quoted in logs.

Reviewed by Cursor Bugbot for commit 390dfd7. Configure here.

@greptile-apps

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR improves diagnostics at several error-swallowing boundaries while preserving fail-closed trace redaction.

  • Adds bounded, structured descriptions of trace-projection failures without logging external exception messages.
  • Preserves underlying upload error causes so database driver codes remain observable.
  • Uses describeError for execution-usage and workspace-save failures.
  • Redacts Drizzle-bound parameter tails from all messages returned by describeError.
  • Adds focused tests for cause traversal, SQLSTATE reporting, parameter redaction, and projection fallback diagnostics.

Confidence Score: 5/5

The PR appears safe to merge with no concrete blocking or non-blocking defects identified.

The changed paths preserve existing fallback, retry, and caller-facing error behavior while making underlying causes serializable and redacting demonstrated database parameter values.

Important Files Changed

FilenameOverview
packages/utils/src/errors.tsExtends describeError to redact Drizzle parameter tails in deepest and cause-chain messages while retaining bounded, cycle-safe cause traversal.
apps/sim/lib/logs/execution/trace-secret-projection.tsAdds structured fallback diagnostics while restricting external failures to their error names and retaining fail-closed behavior.
apps/sim/lib/logs/execution/logger.tsReplaces an unserializable raw ledger error with a redacted structured cause description.
apps/sim/lib/uploads/contexts/workspace/workspace-file-manager.tsPreserves the original upload failure as an Error cause and emits structured diagnostics without changing conflict retry behavior.
apps/sim/lib/uploads/contexts/workspace/fetch-external-url.tsReplaces the raw swallowed save error with a serializable, redacted cause description.
packages/utils/src/errors.test.tsCovers parameter redaction for wrapped and unwrapped database errors.
apps/sim/lib/logs/execution/trace-secret-projection.test.tsVerifies invariant diagnostics, external-message withholding, and structural fallback behavior.
apps/sim/lib/uploads/contexts/workspace/workspace-file-storage-accounting.test.tsVerifies that upload wrapping preserves the deepest database SQLSTATE and message.
apps/sim/lib/uploads/contexts/workspace/fetch-external-url.test.tsVerifies structured reporting of the database driver cause behind swallowed workspace-save failures.
apps/sim/lib/logs/execution/logger.test.tsVerifies structured SQLSTATE reporting when usage-ledger writes fail.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
E[Underlying error] --> W[Error wrapper with cause]
W --> D[describeError]
D --> R[Redact bound parameters]
R --> L[Structured diagnostic log]
P[Trace projection failure] --> T{Internal projection error?}
T -->|Yes| I[Log static invariant reason]
T -->|No| N[Log error name only]
I --> F[Fail-closed structural fallback]
N --> F
Loading

Reviews (1): Last reviewed commit: "fix(observability): attach the real caus..." | Re-trigger Greptile

…ites
Three log sites discarded the underlying error, which blocked root-cause
analysis in production.
- Trace secret projection swallowed TraceSecretProjectionError in four
catch blocks (per-field omission, whole-tree fallback, post-transform
invariant, structural traversal) across ~30 distinct throw sites, so no
warning said which invariant fired. Each now reports the failure. Only
TraceSecretProjectionError messages are logged — they are fixed literals
describing an invariant. A failure raised outside the module may quote
trace content (a JSON parse error embeds the text it choked on), so those
are reported by name only.
- ExecutionLogger's unbilled-charge error logged `"error":{}` because a
plain Error has non-enumerable message/stack. It now logs describeError.
- WorkspaceFileStorage / FetchExternalUrl logged `saveError:{}` for the
same reason, and the upload wrapper rethrew without a cause, so Drizzle's
`Failed query:` wrapper dropped the Postgres SQLSTATE. The wrapper now
chains the cause and both sites log describeError, which reports the
deepest link's code.
describeError additionally strips the `params:` tail Drizzle appends to its
message, so bound parameter values never reach logs.
@waleedlatif1
waleedlatif1force-pushed the fix/error-cause-observability branch from 390dfd7 to a5e0276CompareAugust 6, 2026 19:21
@waleedlatif1
waleedlatif1 merged commit 9b47930 into stagingAug 6, 2026
2 of 3 checks passed
@waleedlatif1
waleedlatif1 deleted the fix/error-cause-observability branch August 6, 2026 19:22
Sign up for freeto 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

@waleedlatif1