CodeIssuesPull RequestsActionsSecurityInsights
✨ AI
More
Settings

fix(archaeology): rendered the provider's raw error, and called it high confidence #5488

Merged⚡ AI-generatedXSccantynz wants to mergefix/archaeology-stops-leaking-provider-errorsmainopened 21d ago
1 changed file+38−4
Modifiedsrc/lib/ai-archaeology.ts+38−4View fileUnifiedSplit
435435Synthesize: why does this code exist? What problem does it solve? What decisions were made?`;
436436
437437 let explanation = "";
438 let explanationFailed = false;
438439 try {
439440 const anthropic = getAnthropic();
440441 const message = await anthropic.messages.create({
446447 });
447448 explanation = extractText(message);
448449 } catch (err) {
449 explanation = `Unable to generate explanation: ${err instanceof Error ? err.message : String(err)}`;
450 // NEVER render an upstream provider's error text to the page.
451 //
452 // This used to interpolate `err.message` straight into `explanation`,
453 // which is displayed to whoever is viewing the repo — including
454 // anonymous visitors on a public repo. Anthropic's SDK errors carry
455 // operator billing state verbatim ("Your credit balance is too low to
456 // access the Anthropic API..."), so an outage published our account
457 // status to strangers. Observed 2026-08-11.
458 //
459 // The operator still needs the real reason, so it goes to the server log
460 // where it belongs, and the page gets a message that is true and useless
461 // to an attacker.
462 console.error("[archaeology] explanation generation failed:", err);
463 explanationFailed = true;
464 explanation =
465 "The AI explanation could not be generated right now. The findings below come " +
466 "from git history and are unaffected.";
450467 }
451468
452469 // Step 6: Build findings list sorted by date desc
498515 return db2 - da;
499516 });
500517
501 const confidence = deriveConfidence(commits, prs, relatedIssues);
518 // `deriveConfidence` scores the GIT EVIDENCE — how many commits, PRs and
519 // issues we found. It knows nothing about whether the AI step worked, so a
520 // file with rich history scored "high" even when the explanation had just
521 // failed. The page renders the two side by side, so a repo could display
522 // "High confidence" directly beside an upstream error message.
523 //
524 // A report whose central claim could not be generated is not high
525 // confidence, whatever the git log says. Clamp it.
526 const confidence = explanationFailed
527 ? "low"
528 : deriveConfidence(commits, prs, relatedIssues);
502529
503530 const report: ArchaeologyReport = {
504531 filePath,
512539 setCached(repoId, filePath, report);
513540 return report;
514541 } catch (err) {
515 // Never throws — return a degraded report
542 // Never throws — return a degraded report.
543 //
544 // Same rule as the inner catch: the real error goes to the log, not to the
545 // viewer. This path can surface database and filesystem errors, which leak
546 // internal paths and schema just as readily as a provider error leaks
547 // billing state.
548 console.error("[archaeology] report generation failed:", err);
516549 return {
517550 filePath,
518551 query,
519 explanation: `Archaeology failed: ${err instanceof Error ? err.message : String(err)}`,
552 explanation:
553 "Archaeology could not run for this file. The error has been logged for the operator.",
520554 findings: [],
521555 confidence: "low",
522556 analyzedAt: new Date(),
523557
c comment · e edit title · m merge · a approve · r request changes · ? shortcuts