feat(telemetry): add masked error message and source stack frames to `error_fingerprint`

Merged
AN
3d ago
+550-15

Issue for this PR

None.

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Today error_fingerprint carries only a hash of the error. You can tell an error is frequent, but not what kind of error it is. This adds one optional field, error_message, built so that user text cannot reach it.

error_message is built from a fixed vocabulary. The first line of the error is split into words, and each word is kept only if it is in ERROR_VOCABULARY, a sorted list of 363 common error words such as "cannot", "delete", "file", "permission", "denied", "connection", "refused" and "timeout". Every other run of characters becomes <x>, and the result is capped at 200 characters. The output can only ever contain vocabulary words, <x> and spaces. That rules out identifiers, paths, numbers, quoted values, names, SQL objects and non-ASCII text.

The vocabulary deliberately leaves out credential words (password, secret, token, key, auth, cookie, session, bearer and similar), so they never appear.

Example: Cannot delete file /Users/alice/acme.sql: permission denied becomes cannot delete file <x> permission denied.

maskString and the fingerprint hash input are unchanged. The new code is in src/altimate/telemetry/error-detail.ts, and session/prompt.ts calls ErrorDetail.describeToolError and ErrorDetail.buildErrorFingerprintEvent. docs/docs/reference/telemetry.md is updated: the error_fingerprint row, and the Privacy section.

Earlier versions of this PR used regex masking and then a word-shape template. Review kept finding new ways for user text to get through, such as quotes, escapes, Unicode and credential-label forms. The fixed vocabulary closes that class by construction. The cost is that a message only stays readable when its words are in the list, and the list is a judgment call worth a look.

How did you verify your code works?

  • bun test test/telemetry test/altimate/telemetry-safety.test.ts: 410 pass, 0 fail.
    • A property-style test runs 21 adversarial inputs and asserts that every output token is <x> or a vocabulary word, with no credential word and no user word. The inputs cover alphabetic and compound credentials; typographic, backtick and escaped quotes; paths with spaces; SQL with identifiers; names; base64; emails; URLs; and non-ASCII text.
    • Other tests cover readability examples, that the vocabulary is sorted and contains no credential words, the event wiring, and that the hash still equals the previous computation.
  • bun run typecheck: clean. Upstream marker check: ok. GitGuardian: no secrets.

Screenshots / recordings

Not a UI change.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

🤖 Generated with Claude Code