cc-haha/src/server/services/oauthRefreshLog.ts
程序员阿江(Relakkes) 837337901d fix(diagnostics): stop logging expected states as errors and isolate tests
The diagnostics panel filled with red ERROR/WARN entries during normal use
because the capture layer treated "wrote to console" as "something failed",
with no severity gatekeeping. Much of the noise was also test runs leaking
into the user's real ~/.claude/cc-haha/diagnostics.

- diagnosticsService: default unclassified events to info (not error);
  drop writes under NODE_ENV=test when no CLAUDE_CONFIG_DIR is set; document
  the console-capture contract (expected states use console.debug/info).
- index: don't install console/process capture under bun test.
- api/diagnostics: an ingested event with missing severity defaults to info.
- oauthRefreshLog (new): token refresh failure is gracefully handled and is
  never an error — expected expiry (401/403/revoked) logs at debug, anything
  else at warn. Wired into both Haha OAuth services.
- conversationService: classify cli_runtime_exit severity by exit code, so
  clean/SIGTERM/SIGKILL exits are info and only abnormal codes stay error
  (real "chat died" crashes remain perceivable).
- ws/handler: streaming partial tool-input JSON is normal — debug, not warn.

Tests: oauth-refresh-log + cli-exit-severity unit tests; diagnostics-service
covers the info default and the test-isolation guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 20:12:45 +08:00

33 lines
1.5 KiB
TypeScript

/**
* Shared logging policy for OAuth token-refresh failures.
*
* A refresh failure is a normal, gracefully-handled lifecycle event: the
* caller returns null and the UI prompts the user to sign in again. It is
* therefore never a program "error" and must not be logged via console.error
* (which the diagnostics service captures as a red ERROR event — see
* diagnosticsService.installConsoleCapture).
*
* - Expected expiry (token revoked / 401 / 403 / invalid_grant): the everyday
* "your session ended, log in again" case → console.debug (not captured).
* - Anything else (network error, 5xx, malformed response): still worth a
* breadcrumb but not a failure of our program → console.warn.
*/
const EXPECTED_AUTH_EXPIRY_RE =
/(refresh revoked|invalid_grant|\b401\b|\b403\b|unauthorized|forbidden)/i
export function isExpectedAuthRefreshFailure(error: unknown): boolean {
const message = error instanceof Error ? error.message : String(error ?? '')
return EXPECTED_AUTH_EXPIRY_RE.test(message)
}
export function logTokenRefreshFailure(serviceLabel: string, error: unknown): void {
const message = error instanceof Error ? error.message : error
if (isExpectedAuthRefreshFailure(error)) {
// Expected: token expired/revoked. Quiet — the user just needs to re-auth.
console.debug(`${serviceLabel} token refresh failed (expected, re-auth required):`, message)
return
}
console.warn(`${serviceLabel} token refresh failed:`, message)
}