From cb04a4a39880890c63b719fcc483b7cda6632126 Mon Sep 17 00:00:00 2001 From: bohdansolovie Date: Tue, 21 Jul 2026 17:13:07 +0200 Subject: [PATCH] fix(queue): stamp console.error payloads as level error Twenty-four queue failure logs used console.error with an explicit level:"warn", which made forwardStructuredLogToSentry treat real failures as warnings. Align them with the sink and the other console.error call sites in the same files. Closes #7806 Co-authored-by: Cursor --- src/queue/ai-review-orchestration.ts | 2 +- src/queue/processors.ts | 44 +++++++------- src/queue/slop-detection.ts | 2 +- test/unit/queue-console-error-level.test.ts | 65 +++++++++++++++++++++ 4 files changed, 89 insertions(+), 24 deletions(-) create mode 100644 test/unit/queue-console-error-level.test.ts diff --git a/src/queue/ai-review-orchestration.ts b/src/queue/ai-review-orchestration.ts index 90122d51e4..4207911ee5 100644 --- a/src/queue/ai-review-orchestration.ts +++ b/src/queue/ai-review-orchestration.ts @@ -880,7 +880,7 @@ export async function runAiReviewForAdvisory( } catch (error) { console.error( JSON.stringify({ - level: "warn", + level: "error", event: "ai_review_failed", repository: args.repoFullName, pullNumber: args.pr.number, diff --git a/src/queue/processors.ts b/src/queue/processors.ts index fbdff23404..520432d05b 100644 --- a/src/queue/processors.ts +++ b/src/queue/processors.ts @@ -805,7 +805,7 @@ export async function buildContributorDecisionPacks( /* v8 ignore next -- defensive per-login isolation; the log-and-continue path is not exercised in tests */ console.error( JSON.stringify({ - level: "warn", + level: "error", event: "decision_pack_login_failed", login: contributorLogin, error: errorMessage(error), @@ -1474,7 +1474,7 @@ export async function sweepRepoRegate( ).catch((error) => { console.error( JSON.stringify({ - level: "warn", + level: "error", event: "sweep_mark_regated_failed", repository: repoFullName, error: errorMessage(error), @@ -1775,7 +1775,7 @@ export async function sweepRepoBacklogConvergence( ).catch((error) => { console.error( JSON.stringify({ - level: "warn", + level: "error", event: "backlog_convergence_mark_regated_failed", repository: repoFullName, error: errorMessage(error), @@ -1919,7 +1919,7 @@ export async function regatePullRequest( } console.error( JSON.stringify({ - level: "warn", + level: "error", event: "sweep_rereview_failed", deliveryId, repository: repoFullName, @@ -3584,7 +3584,7 @@ export async function reReviewStoredPullRequest( if (isGitHubRateLimitedError(error) || isRetryableJobError(error)) throw error; console.error( JSON.stringify({ - level: "warn", + level: "error", event: "pr_public_surface_failed", deliveryId, repository: repoFullName, @@ -3618,7 +3618,7 @@ export async function reReviewStoredPullRequest( ).catch((error) => { console.error( JSON.stringify({ - level: "warn", + level: "error", event: "agent_maintenance_failed", deliveryId, repository: repoFullName, @@ -5139,7 +5139,7 @@ async function processContributorEvidenceLogins( /* v8 ignore next -- defensive per-login isolation; the log-and-continue path is not exercised in tests */ console.error( JSON.stringify({ - level: "warn", + level: "error", event: "contributor_evidence_login_failed", login: contributorLogin, error: errorMessage(error), @@ -6388,7 +6388,7 @@ async function handlePullRequestWebhookEvent( if (isGitHubRateLimitedError(error) || isRetryableJobError(error)) throw error; console.error( JSON.stringify({ - level: "warn", + level: "error", event: "pr_public_surface_failed", deliveryId, repository: payload.repository?.full_name, @@ -6426,7 +6426,7 @@ async function handlePullRequestWebhookEvent( /* v8 ignore next -- best-effort: auto-maintain failures are logged, never surfaced to the gate. */ console.error( JSON.stringify({ - level: "warn", + level: "error", event: "agent_maintenance_failed", deliveryId, repository: repoFullName, @@ -6457,7 +6457,7 @@ async function handlePullRequestWebhookEvent( /* v8 ignore next -- best-effort: a reputation-record failure is logged, never surfaced to the gate. */ console.error( JSON.stringify({ - level: "warn", + level: "error", event: "reputation_record_failed", deliveryId, repository: repoFullName, @@ -6483,7 +6483,7 @@ async function handlePullRequestWebhookEvent( /* v8 ignore next -- best-effort: a RAG re-index enqueue failure is logged, never surfaced to the gate. */ console.error( JSON.stringify({ - level: "warn", + level: "error", event: "rag_reindex_enqueue_failed", deliveryId, repository: repoFullName, @@ -6508,7 +6508,7 @@ async function handlePullRequestWebhookEvent( /* v8 ignore next -- best-effort: a sibling re-gate enqueue failure is logged, never surfaced to the gate. */ console.error( JSON.stringify({ - level: "warn", + level: "error", event: "sibling_regate_enqueue_failed", deliveryId, repository: repoFullName, @@ -6612,7 +6612,7 @@ async function handleIssueWebhookEvent( /* v8 ignore next -- best-effort: an issue-cap enforcement failure is logged, never surfaced to the webhook. */ console.error( JSON.stringify({ - level: "warn", + level: "error", event: "contributor_issue_cap_failed", deliveryId, repository: payload.repository?.full_name, @@ -7013,7 +7013,7 @@ async function resolvePullRequestFilesForReview( /* v8 ignore next -- fail-safe: an inline fetch failure degrades to the empty stored rows (byte-identical to pre-fix). */ console.error( JSON.stringify({ - level: "warn", + level: "error", event: "review_files_inline_fetch_failed", repository: args.repoFullName, pullNumber: args.pullNumber, @@ -7479,7 +7479,7 @@ export async function runLinkedIssueSatisfactionForAdvisory( } catch (error) { console.error( JSON.stringify({ - level: "warn", + level: "error", event: "linked_issue_satisfaction_failed", repository: args.repoFullName, pullNumber: args.pr.number, @@ -7557,7 +7557,7 @@ export async function runContentLaneDeliverableCheckForAdvisory( * calls (e.g. a DB-backed cache layer) degrades to "no finding" instead of an unhandled rejection. */ console.error( JSON.stringify({ - level: "warn", + level: "error", event: "content_lane_deliverable_check_failed", repository: args.repoFullName, pullNumber: primaryIssueNumber, @@ -9058,12 +9058,12 @@ async function maybePublishPrPublicSurface( // Stamp the head SHA only after every required public surface for this repo completed. For gate-enabled repos, // a comment/label without a finalized Orb gate check is incomplete and must stay repair-visible to the sweep. await markPullRequestSurfacePublished(env, repoFullName, pr.number, advisory.headSha).catch((error) => { - console.error(JSON.stringify({ level: "warn", event: "surface_published_mark_failed", repoFullName, pullNumber: pr.number, error: errorMessage(error) })); + console.error(JSON.stringify({ level: "error", event: "surface_published_mark_failed", repoFullName, pullNumber: pr.number, error: errorMessage(error) })); }); // #regate-churn: mark the AI review row for THIS head+fingerprint as durably published (a no-op when no fresh // row was written this pass -- e.g. the frozen-reuse path above, or AI review off/skipped entirely). await markAiReviewPublished(env, repoFullName, pr.number, advisory.headSha).catch((error) => { - console.error(JSON.stringify({ level: "warn", event: "ai_review_published_mark_failed", repoFullName, pullNumber: pr.number, error: errorMessage(error) })); + console.error(JSON.stringify({ level: "error", event: "ai_review_published_mark_failed", repoFullName, pullNumber: pr.number, error: errorMessage(error) })); }); return gateEvaluation; }; @@ -10374,7 +10374,7 @@ async function maybePublishPrPublicSurface( }).catch((error) => { console.error( JSON.stringify({ - level: "warn", + level: "error", event: "gate_check_summary_upsert_failed", repoFullName, pullNumber: pr.number, @@ -10466,7 +10466,7 @@ async function maybePublishPrPublicSurface( }).catch((error) => { console.error( JSON.stringify({ - level: "warn", + level: "error", event: "gate_check_summary_upsert_failed", repoFullName, pullNumber: pr.number, @@ -10512,7 +10512,7 @@ async function maybePublishPrPublicSurface( }).catch((error) => { console.error( JSON.stringify({ - level: "warn", + level: "error", event: "gate_check_summary_upsert_failed", repoFullName, pullNumber: pr.number, @@ -10554,7 +10554,7 @@ async function maybePublishPrPublicSurface( }).catch((error) => { console.error( JSON.stringify({ - level: "warn", + level: "error", event: "gate_check_summary_upsert_failed", repoFullName, pullNumber: pr.number, diff --git a/src/queue/slop-detection.ts b/src/queue/slop-detection.ts index 00fd749b78..4a1ecdf85d 100644 --- a/src/queue/slop-detection.ts +++ b/src/queue/slop-detection.ts @@ -172,7 +172,7 @@ export async function runAiSlopForAdvisory( } catch (error) { console.error( JSON.stringify({ - level: "warn", + level: "error", event: "ai_slop_failed", repository: args.repoFullName, pullNumber: args.pr.number, diff --git a/test/unit/queue-console-error-level.test.ts b/test/unit/queue-console-error-level.test.ts new file mode 100644 index 0000000000..226760c68d --- /dev/null +++ b/test/unit/queue-console-error-level.test.ts @@ -0,0 +1,65 @@ +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; + +// #7806 — console.error sinks must not stamp level:"warn" (Sentry severity follows the explicit +// level field over the sink default). Keep the three queue files consistent with the rest of +// their console.error call sites. +const QUEUE_FILES = [ + "src/queue/processors.ts", + "src/queue/slop-detection.ts", + "src/queue/ai-review-orchestration.ts", +] as const; + +function consoleErrorBlocks(source: string): string[] { + const blocks: string[] = []; + let i = 0; + while (true) { + const start = source.indexOf("console.error(", i); + if (start < 0) break; + let k = start + "console.error(".length; + let depth = 1; + while (k < source.length && depth > 0) { + const ch = source[k]!; + if (ch === "(") depth += 1; + else if (ch === ")") depth -= 1; + else if (ch === '"' || ch === "'" || ch === "`") { + const quote = ch; + k += 1; + while (k < source.length) { + if (source[k] === "\\") { + k += 2; + continue; + } + if (source[k] === quote) break; + k += 1; + } + } + k += 1; + } + blocks.push(source.slice(start, k)); + i = k; + } + return blocks; +} + +describe("queue console.error level matches the error sink (#7806)", () => { + it("never stamps level:\"warn\" inside console.error payloads in the queue pipeline", () => { + const mismatches: string[] = []; + for (const rel of QUEUE_FILES) { + const source = readFileSync(join(process.cwd(), rel), "utf8"); + for (const block of consoleErrorBlocks(source)) { + if (block.includes('level: "warn"') || block.includes("level: 'warn'")) { + mismatches.push(rel); + } + } + } + expect(mismatches).toEqual([]); + }); + + it("keeps at least one representative console.error payload at level:\"error\"", () => { + const source = readFileSync(join(process.cwd(), "src/queue/slop-detection.ts"), "utf8"); + expect(source).toMatch(/console\.error\(\s*JSON\.stringify\(\s*\{\s*level:\s*"error"/); + expect(source).toContain('event: "ai_slop_failed"'); + }); +});