godcrm/backend/services/agent-job/auth-error.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

52 lines
2 KiB
JavaScript

/**
* ADR-0057 WP-B — Auth-error matcher for Claude CLI output.
*
* Claude Code CLI surfaces Anthropic API 401s (OAuth-token rotation race during
* parallel CLI processes) as plain assistant text. Before WP-B those strings
* were persisted via saveStepMessage as content_type='text' and rendered like
* the agent's actual response. See conversation 3178 msg #558510 incident.
*
* This module exposes a single matcher used by agent-job/create.js to detect
* the auth-error final response and route it to an agent_status row instead.
*/
const AUTH_ERROR_PATTERNS = [
/Failed to authenticate\. API Error: 401/i,
/Invalid authentication credentials/i,
/authentication_error/i,
/invalid_api_key/i,
/OAuth token expired/i,
];
export function isAuthError(text) {
if (typeof text !== 'string' || !text) return false;
return AUTH_ERROR_PATTERNS.some((re) => re.test(text));
}
/**
* ADR-0057 WP-B (step-level extension): decide whether a streamed assistant
* content block should be suppressed from chat because it's a leaked CLI
* auth-error blob.
*
* The original WP-B only guarded the *final* result content. But the Claude CLI
* also surfaces 401s mid-run as a `thinking` or `text` block during OAuth-token
* rotation (see conv 3454 msgs 678432/678434, conv 2068 680051/680053). Those
* leak into chat as raw "Invalid authentication credentials" bubbles AND count
* as step output — which suppresses the empty-result safety net, so the turn
* ends with no final text and only a Continue button.
*/
export function isAuthErrorBlock(block) {
if (!block || typeof block !== 'object') return false;
const text =
block.type === 'thinking' ? block.thinking :
block.type === 'text' ? block.text :
null;
return isAuthError(text);
}
/** Pull the Anthropic request_id out of the error blob, if present. */
export function extractRequestId(text) {
if (typeof text !== 'string') return null;
const m = text.match(/request_id["':\s]+(req_[A-Za-z0-9]+)/);
return m ? m[1] : null;
}