Sloth-ninja tightens error handling without exposing the machinery

JessicaOSS now gives users clearer failure messages while keeping sensitive database and provider details out of view.

securityinfrastructure

This is quiet but important legal-tech work. Sloth-ninja has separated what an operator needs to diagnose a problem from what a client should see when something goes wrong.

The fork now surfaces useful, plain-language failures, while retaining fuller diagnostic context in internal logs. It also treats a known, expected database condition without unnecessary noise and closes a few edge cases that could otherwise produce confusing failures.

The result is a more professional failure mode: people using the product get a helpful response, while the underlying data systems reveal less about themselves. Curious teams can inspect the GitHub changes for the implementation detail.

So what GCs and legal-product teams handling sensitive matters should care because trustworthy software needs to fail clearly without turning an error screen into an information leak.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

8 commits from Sloth-ninja/JessicaOSS, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
28f4eb0c Error-visibility hardening: surface Supabase errors, stop raw-detail leaks sloth-ninja 2026-08-04 ↗ GitHub
commit body
Production incident 04/08/2026: real Supabase errors were invisible.
safeErrorLog/safeErrorMessage flattened PostgrestError plain objects to
the literal "Unexpected error", and 56 route/middleware sites sent the
raw DB/provider error message to the browser while logging nothing
server-side.

- safeError.ts: extract message/code/details/hint from non-Error
  message-bearing objects (known string fields only, always redacted);
  new GENERIC_ERROR_DETAIL constant and failRequest() helper (log
  redacted server-side + fixed generic client detail).
- asyncHandler.ts: use the shared constant.
- middleware/auth.ts: failure paths now console.error (devLog was
  dev-only, so production logged nothing); fixed 401/500 details.
- routes/{chat,projects,documents,tabular,workflows,user}.ts: every
  DB/provider-originated `detail: error.message` send converted to
  failRequest; user.ts 500-status errorMessage catches now log via
  safeErrorLog and send the fixed generic detail.
- New lib/safeError.test.ts: 17 tests pinning extraction, redaction,
  fallbacks and failRequest behaviour.

Hand-written validation details, multer 400s, MCP-connector actionable
errors and redacted SSE stream events left unchanged by design.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5047ffb7 Review fixes: log-only extraction, harden errorMessage(), redact code, quiet 42703 sloth-ninja 2026-08-04 ↗ GitHub
commit body
- safeErrorMessage: revert the object-message widening - extraction is
  LOG-ONLY (all call sites are client-facing SSE/persisted text and libs
  rethrow raw Supabase plain objects); non-Error objects keep the fallback.
  safeErrorLog extracts independently, unchanged.
- routes/user.ts errorMessage(): Error instances keep their actionable
  message (redacted, string code appended); plain objects/strings degrade
  to GENERIC_ERROR_DETAIL - never join details/hint, never JSON.stringify.
  Fixes raw Postgrest text leaking through the MCP-connector 400/404
  sites. Exported for tests; follow-up: retire in favour of safeError.
- safeErrorLog: redact the code field before using it as name.
- middleware/auth.ts: 42703 (pre-migration, tolerated) stays at devLog;
  console.error only for real failures.
- Tests updated/added: safeErrorMessage fallback contract, code
  redaction, errorMessage both shapes (623 total).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2eca1c36 Re-review fixes: unblind errorMessage log sites, log tool-loop originals sloth-ninja 2026-08-04 ↗ GitHub
commit body
- user.ts: all 11 sites that passed errorMessage(err) to console.error now
  log safeErrorLog(err) - wave 2 had made plain-object errors log the
  literal generic sentence, strictly worse than main on the log side.
  errorMessage(err) remains only for the client detail at the seven split
  sites.
- errorMessage(): drop the "(code)" suffix entirely - it duplicated the
  sibling code field in user-visible copy on the oauth_required 401, and
  no other caller needs it.
- chatTools.ts tool-loop catch: console.error the ORIGINAL error via
  safeErrorLog before the AssistantStreamError rethrow, so Postgrest
  objects thrown by tools are visible in production logs.
- Tests: new route-level test (GET /user/api-keys) asserting the log
  receives extracted Postgrest diagnostics while the client gets the
  fixed detail; errorMessage suffix expectation updated. 624 total.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
d89f394b Merge remote-tracking branch 'origin/main' into error-visibility-hardening sloth-ninja 2026-08-04 ↗ GitHub
# Conflicts:
#	docs/BUILD_LOG.md
85ca03b7 Merge pull request #72 from Sloth-ninja/error-visibility-hardening Ezana.H 2026-08-04 ↗ GitHub
Error-visibility hardening: surface Supabase errors, stop raw-detail leaks
4913ee97 Composed-range review fix wave for the #71/#72 train sloth-ninja 2026-08-05 ↗ GitHub
commit body
Six review findings: Clio tool executors log original failures
([clio/tools], validation errors excluded); null Clio body throws
instead of shaping into an authoritative empty matter list; the
at-least-one-filter guard re-applied to decoded page tokens; query
capped at 256 chars at token mint; stream routes log only name/message
for the AssistantStreamError rethrow wrapper; CLAUDE.md safeError.ts
module-map entry + status sentence. Addendum: all seven real-scrypt
suites get a 120s file-level timeout (contention flakes; DURABLE_LESSONS
2026-08-05).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ad46a640 Review micro-wave: honest oversize-token handling, sibling null-body guards, CLAUDE.md accuracy sloth-ninja 2026-08-05 ↗ GitHub
commit body
The 256-char query cap only bounds the common case (reviewer measured
minted tokens over the 1024 decode cap with CJK queries / long cursors):
the mint site now drops any token its own decoder would refuse, keeping
has_more true. findContact and listMatterDocuments get the same
null-body guard as findMatter (deleteTimeEntry untouched - 204 is
success there). CLAUDE.md: stale 604-test figure -> 656 (post-#73),
'#71/#72 deployed' claim corrected to merged-only (deploy pending owner
action), status header date bumped. Three new tests (suite 656).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
8e1023ff Merge pull request #73 from Sloth-ninja/fix-train-composed-findings Ezana.H 2026-08-05 ↗ GitHub
Composed-range review fix wave for the #71/#72 train

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-1003.md from inside the repo you want the changes in.

⬇ Download capture-thread-1003.md