Skip to content

feat: AI analysis for failed builds (migration + edge fn + CLI)#2284

Open
WcaleNieWolny wants to merge 28 commits into
mainfrom
ai-build-analytics
Open

feat: AI analysis for failed builds (migration + edge fn + CLI)#2284
WcaleNieWolny wants to merge 28 commits into
mainfrom
ai-build-analytics

Conversation

@WcaleNieWolny
Copy link
Copy Markdown
Contributor

@WcaleNieWolny WcaleNieWolny commented May 18, 2026

Summary

Single PR covering the capgo half of the AI build analytics feature. Worker side is at Cap-go/capgo_builder#93.

Migration

  • Adds `ai_analyzed BOOLEAN NOT NULL DEFAULT FALSE` on `build_requests` (migration `20260518120000_*`)
  • Surgical update to `supabase.types.ts` (only the `ai_analyzed` field added to Row / Insert / Update — not a full regen, to avoid unrelated drift)

Edge function (`supabase/functions/_backend/public/build/ai_analyze.ts`)

  • New `POST /build/ai_analyze` auths via Capgo API key, checks `build_requests` ownership + `status == 'failed'` + idempotency
  • Proxies to `Cap-go/capgo_builder`'s new `/jobs/:id/ai-analyze`
  • Flag-after-success: if the worker call fails, `ai_analyzed` stays `false` so the user can retry
  • Returns 409 specifically for `already_analyzed` (the CLI branches on this status)
  • Reuses existing `app.build_native` permission

CLI (`cli/`)

  • New `--ai-analytics` flag on `build request [appId]`
  • Automatic `/tmp/capgo-builds/.log` capture in TTY mode (always-on so the user can decide mid-build)
  • Failure control-flow matrix:
    • TTY + flag → menu (Capgo AI / Local AI / Skip)
    • TTY + no flag → ask `Run AI analysis? [y/N]`, then menu
    • CI + flag → auto-upload, analysis to stderr
    • CI + no flag → no-op
  • Local AI option writes `\n\n---LOGS---\n` to `/tmp/capgo-builds/.ai-prompt.txt`
  • Cleanup handlers on `exit` / `SIGINT` / `SIGTERM` / `uncaughtException` (best-effort; `.ai-prompt.txt` preserved if user picked local AI)
  • `SYSTEM_PROMPT` lives in `cli/src/ai/prompt.ts`, byte-identical body to `capgo_builder/src/ai-analyze-prompt.ts`

CI

  • New workflow `.github/workflows/check-ai-prompt-sync.yml` fetches `Cap-go/capgo_builder/src/ai-analyze-prompt.ts` via the GitHub API and diffs against `cli/src/ai/prompt.ts`. Requires repo secret `GH_TOKEN_READ_BUILDER` with read access to `Cap-go/capgo_builder` contents — please provision before merge.

Deferred

  • Python integration test in `capgo_builder/mock-tests/` (Tasks 23-24 of the plan) is deferred because the `mock-tests/` harness isn't on `Cap-go/capgo_builder` `main` yet. Will be added once that lands.

Design doc: docs/superpowers/specs/2026-05-18-capgo-ai-build-analytics-design.md (in this branch).

Test plan

  • vitest: `aiAnalyzeBuild` tests pass (auth, ownership, status, idempotency 409, proxy 5xx flag-not-flipped, proxy 200 flag-flipped) — 6/6 passing locally
  • Migration applies cleanly on preprod
  • Generated Supabase types include `ai_analyzed` field
  • Bun: log-capture utilities tests pass (8/8 locally)
  • Bun: analyze flow tests pass (8/8 locally — matrix, writeLocalAiFile, postAnalyzeRequest)
  • Manual: `node cli/dist/index.js build request --help` shows `--ai-analytics`
  • Manual against preprod with an intentionally-failing build: flag + interactive shows menu; flag + CI prints to stderr; no flag + interactive asks y/N first; second attempt returns 409 already_analyzed
  • CI workflow `check-ai-prompt-sync.yml` passes once both this PR and the capgo_builder PR are merged + secret provisioned

Summary by CodeRabbit

  • New Features

    • Optional AI-assisted build-failure diagnosis that captures build logs and offers remote analysis or a local prompt file, printing formatted Markdown results.
    • Terminal-friendly Markdown renderer for styled AI output.
  • Tests

    • CLI tests for log capture, analyze flow, and Markdown rendering.
    • Server-side tests for the AI analysis endpoint.
  • Chores

    • CI workflow added to enforce byte-identical AI prompt synchronization.

Review Change Stack

Failing tests for aiAnalyzeBuild — module does not exist yet.
Task 13 will implement supabase/functions/_backend/public/build/ai_analyze.ts.
Implements the AI build analysis endpoint that validates permissions,
checks build ownership/status/idempotency, proxies to capgo_builder,
and flips the ai_analyzed flag on success.
Adds test/test-ai-analyze-flow.mjs covering decideAnalyzeBehavior matrix,
writeLocalAiFile, and postAnalyzeRequest (200/409/5xx). Fails with
"Cannot find module '../src/ai/analyze.ts'" until Task 18 implements it.
Adds the --ai-analytics CLI flag to `build request`, types it in the
buildRequestOptionsSchema, and wires the full AI analysis flow in
request.ts: interactive menu (Capgo AI vs local file), CI auto-upload,
ask_then_menu for TTY without flag, and a log-too-big guard path.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds an AI-assisted build-failure analysis flow: a byte-identical SYSTEM_PROMPT with CI sync, TTY-gated per-job log capture, decision/local/remote analyze logic, CLI integration to capture and act on failed builds, a backend proxy endpoint with idempotency, DB typing, and tests.

Changes

AI-Assisted Build Failure Analysis

Layer / File(s) Summary
Prompt constant and CI sync
cli/src/ai/prompt.ts, .github/workflows/check-ai-prompt-sync.yml
Defines SYSTEM_PROMPT instructing AI diagnosis and adds CI workflow that fetches the builder repo prompt and enforces byte-identical sync (skip first 4 header lines).
Log capture infrastructure and tests
cli/src/ai/log-capture.ts, cli/test/test-ai-log-capture.mjs
TTY-gated per-job log capture to an env-overridable base dir, path helpers for logs and AI prompt files, start/append/cleanup APIs, idempotent process-level cleanup handlers, and CLI tests validating lifecycle and filesystem behaviors.
Analysis decision, local prompt, and remote request
cli/src/ai/analyze.ts, cli/test/test-ai-analyze-flow.mjs
Decision logic selecting show_menu/ask_then_menu/auto_upload/skip based on TTY+flag. writeLocalAiFile composes SYSTEM_PROMPT + captured logs. postAnalyzeRequest POSTs to /build/ai_analyze with a 60s timeout and maps responses to `{ kind: 'ok'
CLI wiring, renderer, tests, and package scripts
cli/src/schemas/build.ts, cli/src/index.ts, cli/package.json, cli/src/build/request.ts, cli/src/ai/render-markdown.ts, cli/test/*
Adds optional aiAnalytics schema field and --ai-analytics CLI option. Integrates log capture (start, append, cleanup) into build request flow, wraps streamed logger to persist logs, runs AI failure flow on failed builds with interactive menu/confirm and auto-upload paths, routes large logs to local flow or skip, adds renderMarkdown for ANSI output, and wires new test scripts into package.json.
Backend API endpoint and database
supabase/functions/_backend/utils/supabase.types.ts, supabase/functions/_backend/public/build/ai_analyze.ts, supabase/functions/_backend/public/build/index.ts, tests/build-ai-analyze.test.ts
Adds ai_analyzed boolean to build_requests types. Implements aiAnalyzeBuild that enforces app.build_native permission, validates failed status and idempotency (409), proxies { logs } to ${BUILDER_URL}/jobs/${jobId}/ai-analyze with 60s timeout, validates analysis in response, best-effort updates ai_analyzed on success, and returns analysis. Wires POST /build/ai_analyze. Vitest tests cover auth, state, idempotency, proxy failure, and success paths.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CLI
  participant Disk
  participant API as /functions/v1/build/ai_analyze
  participant Builder as BUILDER_URL/jobs/{jobId}/ai-analyze
  participant DB as Supabase

  User->>CLI: build request (maybe --ai-analytics)
  CLI->>Disk: startCaptureForJob, appendCapturedLine (stream logs)
  CLI->>CLI: build fails, decideAnalyzeBehavior

  alt auto_upload
    CLI->>Disk: read captured logs
    CLI->>API: POST /build/ai_analyze (jobId, appId, logs)
    API->>API: checkPermission, load build_requests
    API->>API: validate failed status & ai_analyzed flag
    API->>Builder: POST /jobs/{jobId}/ai-analyze (logs)
    Builder-->>API: { analysis }
    API->>DB: UPDATE build_requests SET ai_analyzed=true
    API-->>CLI: { analysis }
    CLI->>User: renderMarkdown(analysis) to stdout/stderr
  else show_menu
    CLI->>User: select Capgo AI or local AI or skip
    alt Capgo AI selected
      CLI->>API: same flow as auto_upload
    else local AI selected
      CLI->>Disk: writeLocalAiFile (write SYSTEM_PROMPT + logs)
      CLI->>User: print local prompt file path
    end
  end

  CLI->>Disk: cleanupCapturedJobFiles (on exit/signal)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Cap-go/capgo#2285: Introduces the public.build_requests.ai_analyzed DB migration referenced by this PR.
  • Cap-go/capgo#1983: Modifies build request flow changes that overlap with this PR's logger/capture integration.

Suggested reviewers

  • zinc-builds

Poem

🐰 I nibble logs and stitch a prompt so neat,
I listen to failures, each error and beep.
I hop a path to Builder, or write a local file,
A rabbit's rhyme to help you debug with style.
🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: AI analysis for failed builds (migration + edge fn + CLI)' directly and specifically describes the main changes: adding AI analysis for failed builds across migration, edge function, and CLI layers.
Description check ✅ Passed The PR description thoroughly covers all template sections: summary of changes across migration/edge function/CLI/CI, detailed test plan with specific test counts, but lacks screenshots section (not critical for backend-heavy changes) and manual testing confirmation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ai-build-analytics

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread .github/workflows/check-ai-prompt-sync.yml Fixed
Comment thread cli/src/build/request.ts Fixed
@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented May 18, 2026

Merging this PR will not alter performance

✅ 43 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing ai-build-analytics (e1165b7) with main (107e6ed)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5dc0930f3b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cli/src/ai/analyze.ts Outdated
const res = await fetch(url, {
method: 'POST',
headers: {
'capgo-api-key': input.apikey,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use a supported auth header for AI analyze calls

postAnalyzeRequest sends the API key as capgo-api-key, but middlewareKey only accepts authorization, capgkey, or x-api-key in supabase/functions/_backend/utils/hono_middleware.ts. In practice this makes the new /build/ai_analyze request fail auth even with a valid key, so the CLI AI analysis path cannot succeed.

Useful? React with 👍 / 👎.

Comment thread cli/src/build/request.ts
}

// --- Task 20: AI failure flow ---
if (finalStatus === 'failed' && captureEnabled && capturedJobId) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Allow AI analysis flow in non-interactive builds

The failure-path gate requires captureEnabled && capturedJobId, but captureEnabled is derived from shouldCaptureLogs() (TTY-only). In CI/non-TTY runs this condition is false, so the new --ai-analytics auto-upload path is never reached even though decideAnalyzeBehavior explicitly maps non-interactive+flag to auto_upload.

Useful? React with 👍 / 👎.

Comment thread cli/src/ai/log-capture.ts Outdated
void cleanupCapturedJobFiles(jobId, { keepAiPromptFile: getKeepPromptFile() })
}
const onExit = () => cleanup()
const onSignal = () => { cleanup(); process.exit(130) }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not exit immediately from the global SIGINT cleanup hook

This handler calls process.exit(130) on SIGINT. Because it is registered before the build-specific SIGINT handler in request.ts, Ctrl+C in interactive builds exits the process before the normal cancel flow can call /build/cancel/:jobId, so user cancellation no longer performs graceful remote cancellation.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (2)
cli/src/build/request.ts (1)

1896-1905: ⚡ Quick win

Avoid duplicating path construction logic.

The path ${process.env.CAPGO_AI_LOG_BASE_DIR || '/tmp/capgo-builds'}/${capturedJobId}.log duplicates the logic from log-capture.ts. If the path format changes, this will become inconsistent.

Consider exporting and reusing a getLogPath(jobId) helper from log-capture.ts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/build/request.ts` around lines 1896 - 1905, The logsPath construction
in runCapgoAi duplicates the path logic from log-capture.ts; update runCapgoAi
to import and call a shared helper (e.g., getLogPath(jobId)) exported from
log-capture.ts instead of building `${process.env.CAPGO_AI_LOG_BASE_DIR ||
'/tmp/capgo-builds'}/${capturedJobId}.log` directly: add an import for
getLogPath, replace the logsPath assignment with getLogPath(capturedJobId) and
keep the rest of the file read logic unchanged.
tests/build-ai-analyze.test.ts (1)

67-126: 💤 Low value

Consider using it.concurrent() for parallel test execution.

Per coding guidelines, tests should use it.concurrent() instead of it() to run tests in parallel within the same file for faster CI/CD.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/build-ai-analyze.test.ts` around lines 67 - 126, Replace the
synchronous test calls with concurrent ones inside the
describe('aiAnalyzeBuild', ...) block: change each it( ... ) to it.concurrent(
... ) for all test cases (the tests that call aiAnalyzeBuild,
mockCheckPermission, mockBuildRequestRow, and use global.fetch), ensuring you
update every occurrence in that block so the suite runs tests in parallel.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/check-ai-prompt-sync.yml:
- Around line 14-17: The workflow job check-prompt-sync currently relies on
default GITHUB_TOKEN scopes; add an explicit permissions block for least
privilege (at minimum set contents: read) to the job definition so the job uses
the restricted token scope. Update the jobs.check-prompt-sync section to include
a permissions: block (e.g., permissions: { contents: read }) directly under the
job name so GitHub applies the limited token for that job.

In `@cli/src/ai/analyze.ts`:
- Around line 1-3: Reorder the import block so built-in modules
(node:fs/promises) come first, then external packages, then local modules
(SYSTEM_PROMPT, getLogCapturePath, getAiPromptPath) to satisfy import-order
rules; ensure any if statements (look for conditionals using
getAiPromptPath/getLogCapturePath or around the ANALYZE logic) have a blank line
after the closing brace as required by newline-after-if; and fix operator
linebreaks by placing operators at the end of the previous line (not at the
start of the next line) in expressions around string concatenation or multi-line
conditions (search for lines near where SYSTEM_PROMPT is combined or where
readFile/writeFile/stat results are used). Run the linter to confirm all ESLint
errors are resolved before committing.
- Around line 39-40: The PostAnalyzeResult type includes a { kind: 'too_big' }
variant but postAnalyzeRequest never returns it; modify postAnalyzeRequest to
explicitly detect an HTTP 413 response from the backend (check response.status
=== 413) and return { kind: 'too_big' } before the generic error handling path
that produces { kind: 'error', ... }; update any fetch/response branches in
postAnalyzeRequest so 413 is handled early and not treated as a generic error.

In `@cli/src/ai/log-capture.ts`:
- Line 1: Reformat the top-level imports and the offending lines to satisfy
ESLint: reorder the import so it follows the project's import ordering
(third-party and local imports first, then builtin node:fs/promises) and keep a
single import statement per line for mkdir, unlink, writeFile, appendFile;
ensure any multi-statement lines (around the usages of writeFile/appendFile at
the mentioned spots) are split so each statement is on its own line; replace
string concatenation with template literals where required and normalize
newlines/semicolons to match the repo style so bun run cli:test no longer flags
import ordering, template usage, or single-statement-per-line/newline
violations.
- Around line 63-68: The onUncaught handler currently only calls cleanup() which
prevents Node's default fatal-exit; update the uncaughtException listener (the
onUncaught function used in process.once('uncaughtException', onUncaught)) to
perform cleanup and then terminate the process (e.g., call process.exit(1)) or
rethrow the error so the process doesn't continue in a corrupted state; ensure
cleanup() is awaited if it returns a promise before exiting.

In `@cli/src/build/request.ts`:
- Around line 1606-1617: The registered cleanup handler stored in
_unregisterCleanup is never called; update the flow that handles capture (around
startCaptureForJob/registerCleanupHandlers and keepPromptFile) to ensure
_unregisterCleanup is invoked when the build completes or errors (e.g., in a
finally block), using optional chaining like _unregisterCleanup?.() and then set
_unregisterCleanup = null so the exit/signal handlers registered by
registerCleanupHandlers are properly unregistered when done.

In `@supabase/functions/_backend/public/build/ai_analyze.ts`:
- Around line 79-86: The fetch call that assigns builderResp (calling
`${builderUrl}/jobs/${jobId}/ai-analyze` with headers using builderApiKey and
body JSON.stringify({ logs })) needs a timeout: create an AbortController, pass
its signal into the fetch options, start a timer (configurable or reasonable
default) that calls controller.abort() when exceeded, clear the timer on
successful response, and handle the abort error path so the function
returns/throws a clear timeout error rather than hanging; update the fetch
invocation to include the signal and ensure any cleanup/try/catch around
builderResp handles aborted requests.

In `@tests/build-ai-analyze.test.ts`:
- Around line 63-65: Replace uses of the Node-restricted global identifier and
the unnecessary TypeScript suppression with the standard globalThis: change
occurrences where the test assigns the mock (e.g., the line setting global.fetch
= vi.fn() and the other occurrences at the places referenced in the comment) to
use globalThis.fetch = vi.fn() and remove the leading "// `@ts-expect-error`"
directive; update the same replacement at the other occurrences noted (the
references around lines 99, 110, and 120 in tests/build-ai-analyze.test.ts) so
all fetch mocks use globalThis.fetch consistently.

---

Nitpick comments:
In `@cli/src/build/request.ts`:
- Around line 1896-1905: The logsPath construction in runCapgoAi duplicates the
path logic from log-capture.ts; update runCapgoAi to import and call a shared
helper (e.g., getLogPath(jobId)) exported from log-capture.ts instead of
building `${process.env.CAPGO_AI_LOG_BASE_DIR ||
'/tmp/capgo-builds'}/${capturedJobId}.log` directly: add an import for
getLogPath, replace the logsPath assignment with getLogPath(capturedJobId) and
keep the rest of the file read logic unchanged.

In `@tests/build-ai-analyze.test.ts`:
- Around line 67-126: Replace the synchronous test calls with concurrent ones
inside the describe('aiAnalyzeBuild', ...) block: change each it( ... ) to
it.concurrent( ... ) for all test cases (the tests that call aiAnalyzeBuild,
mockCheckPermission, mockBuildRequestRow, and use global.fetch), ensuring you
update every occurrence in that block so the suite runs tests in parallel.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7ee945cc-ee92-4a71-b374-6869c7ea2000

📥 Commits

Reviewing files that changed from the base of the PR and between 58c8448 and 5dc0930.

📒 Files selected for processing (15)
  • .github/workflows/check-ai-prompt-sync.yml
  • cli/package.json
  • cli/src/ai/analyze.ts
  • cli/src/ai/log-capture.ts
  • cli/src/ai/prompt.ts
  • cli/src/build/request.ts
  • cli/src/index.ts
  • cli/src/schemas/build.ts
  • cli/test/test-ai-analyze-flow.mjs
  • cli/test/test-ai-log-capture.mjs
  • supabase/functions/_backend/public/build/ai_analyze.ts
  • supabase/functions/_backend/public/build/index.ts
  • supabase/functions/_backend/utils/supabase.types.ts
  • supabase/migrations/20260518120000_add_ai_analyzed_to_build_requests.sql
  • tests/build-ai-analyze.test.ts

Comment thread .github/workflows/check-ai-prompt-sync.yml Outdated
Comment thread cli/src/ai/analyze.ts Outdated
Comment thread cli/src/ai/analyze.ts Outdated
Comment thread cli/src/ai/log-capture.ts Outdated
Comment thread cli/src/ai/log-capture.ts Outdated
Comment thread cli/src/build/request.ts Outdated
Comment on lines +1606 to +1617
// --- Task 19: /tmp log capture setup ---
const captureEnabled = shouldCaptureLogs()
let capturedJobId: string | null = null
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let _unregisterCleanup: (() => void) | null = null
let keepPromptFile = false // Task 20: mutable so local-AI flow can set it true

if (captureEnabled && buildRequest.job_id) {
capturedJobId = buildRequest.job_id
await startCaptureForJob(buildRequest.job_id)
_unregisterCleanup = registerCleanupHandlers(buildRequest.job_id, () => keepPromptFile)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

_unregisterCleanup is assigned but never called.

The cleanup handler is registered but never unregistered when the build completes normally. This means the process exit/signal handlers remain attached, which could cause issues if this function is called multiple times (e.g., in SDK usage) or if cleanup runs unexpectedly after the function returns.

🔧 Proposed fix
-    // eslint-disable-next-line `@typescript-eslint/no-unused-vars`
-    let _unregisterCleanup: (() => void) | null = null
+    let unregisterCleanup: (() => void) | null = null
     let keepPromptFile = false // Task 20: mutable so local-AI flow can set it true
 
     if (captureEnabled && buildRequest.job_id) {
       capturedJobId = buildRequest.job_id
       await startCaptureForJob(buildRequest.job_id)
-      _unregisterCleanup = registerCleanupHandlers(buildRequest.job_id, () => keepPromptFile)
+      unregisterCleanup = registerCleanupHandlers(buildRequest.job_id, () => keepPromptFile)
     }

Then call unregisterCleanup?.() before returning from the function (in the finally block or after the AI flow completes).

🧰 Tools
🪛 ESLint

[error] 1609-1609: Definition for rule '@typescript-eslint/no-unused-vars' was not found.

(@typescript-eslint/no-unused-vars)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/build/request.ts` around lines 1606 - 1617, The registered cleanup
handler stored in _unregisterCleanup is never called; update the flow that
handles capture (around startCaptureForJob/registerCleanupHandlers and
keepPromptFile) to ensure _unregisterCleanup is invoked when the build completes
or errors (e.g., in a finally block), using optional chaining like
_unregisterCleanup?.() and then set _unregisterCleanup = null so the exit/signal
handlers registered by registerCleanupHandlers are properly unregistered when
done.

Comment thread supabase/functions/_backend/public/build/ai_analyze.ts Outdated
Comment thread tests/build-ai-analyze.test.ts Outdated
Without this, the CI auto_upload branch returned by decideAnalyzeBehavior
never had logs to send because the capture gate was TTY-only. Also drops
the no-op _unregisterCleanup local var.
Calling process.exit(130) from our cleanup handler ran before the build
command's own SIGINT handler, so Ctrl+C skipped the graceful
/build/cancel/:jobId call. The cleanup now only removes /tmp files and
returns; Node exits naturally when no other handler defers it.
- Handle HTTP 413 explicitly in postAnalyzeRequest -> { kind: 'too_big' }
  so the type variant we declared is actually reachable; backend can use 413
  when it rejects oversized logs (cli/src/ai/analyze.ts + new bun test).
- Add 60s AbortSignal.timeout to the edge fn's builder fetch so a hung
  Workers AI call fails fast with builder_error instead of leaving the
  edge fn open until the platform's 150s wall-clock timeout.
- uncaughtException handler now rethrows after cleanup (via setImmediate)
  so Node's default print+exit behavior is preserved; signal handlers
  still yield to the build's own SIGINT cancel flow.
- Lint cleanup auto-applied via 'bun run lint:fix' (import ordering,
  template literals, max-statements-per-line, etc.) across analyze.ts,
  log-capture.ts, request.ts.
- Tests: replace 'global.fetch' with 'globalThis.fetch' and drop the
  unused @ts-expect-error directive (eslint no-restricted-globals).
- New test: postAnalyzeRequest returns too_big on 413.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/build-ai-analyze.test.ts (1)

67-124: ⚡ Quick win

Use it.concurrent() for this suite to match test parallelization rules.

These new tests currently use it(...); please convert to it.concurrent(...) and keep mocks isolated per test as needed.

As per coding guidelines, "Design all tests for parallel execution across files; use it.concurrent() instead of it() to run tests in parallel within the same file for faster CI/CD".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/build-ai-analyze.test.ts` around lines 67 - 124, Convert each test
invocation from it(...) to it.concurrent(... ) in the aiAnalyzeBuild suite (all
tests that call aiAnalyzeBuild, mockCheckPermission, mockBuildRequestRow, and
globalThis.fetch), and ensure mocks remain isolated by resetting/clearing them
in a beforeEach (e.g., jest.clearAllMocks()/mockReset) so each concurrent test
sets up its own mockCheckPermission/mockBuildRequestRow/globalThis.fetch state;
verify you update the test cases that assert updateEqApp and fetch call
parameters to use it.concurrent as well.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cli/src/build/request.ts`:
- Around line 1902-1908: The catch block that silently returns when reading
logsPath fails should emit a short stderr error so users can diagnose failed
auto-upload paths; update the try/catch around the dynamic import and readFile
(the block that reads logsPath) to log a concise message to stderr (e.g., via
console.error or process.stderr.write) including the logsPath and the caught
error message, then return as before to preserve behavior.

In `@tests/build-ai-analyze.test.ts`:
- Line 36: The failures come from lint rules about inline object type delimiters
and formatting of if/newline and chained calls; fix by extracting the inline
parameter type into a named type or interface (e.g., type MockBuildRequestRow =
{ app_id: string; status: string; ai_analyzed: boolean } | null) and use that
type in mockBuildRequestRow(row: MockBuildRequestRow), and then update the
affected blocks (the mockBuildRequestRow declaration and the other occurrences
at the listed locations) to follow consistent chaining and if-newline style:
ensure multi-line if statements put the body on the next line and break long
chains so each dot-starting call is on its own line. Apply these same changes to
the other instances noted (lines ~59-60, 70, 77, 84, 92, 101) to satisfy
style/member-delimiter-style, antfu/if-newline, and antfu/consistent-chaining.

---

Nitpick comments:
In `@tests/build-ai-analyze.test.ts`:
- Around line 67-124: Convert each test invocation from it(...) to
it.concurrent(... ) in the aiAnalyzeBuild suite (all tests that call
aiAnalyzeBuild, mockCheckPermission, mockBuildRequestRow, and globalThis.fetch),
and ensure mocks remain isolated by resetting/clearing them in a beforeEach
(e.g., jest.clearAllMocks()/mockReset) so each concurrent test sets up its own
mockCheckPermission/mockBuildRequestRow/globalThis.fetch state; verify you
update the test cases that assert updateEqApp and fetch call parameters to use
it.concurrent as well.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bb660968-a749-4e38-9064-55e77d230156

📥 Commits

Reviewing files that changed from the base of the PR and between 5dc0930 and 8707303.

📒 Files selected for processing (7)
  • .github/workflows/check-ai-prompt-sync.yml
  • cli/src/ai/analyze.ts
  • cli/src/ai/log-capture.ts
  • cli/src/build/request.ts
  • cli/test/test-ai-analyze-flow.mjs
  • supabase/functions/_backend/public/build/ai_analyze.ts
  • tests/build-ai-analyze.test.ts
✅ Files skipped from review due to trivial changes (1)
  • .github/workflows/check-ai-prompt-sync.yml
🚧 Files skipped from review as they are similar to previous changes (3)
  • cli/test/test-ai-analyze-flow.mjs
  • cli/src/ai/log-capture.ts
  • supabase/functions/_backend/public/build/ai_analyze.ts

Comment thread cli/src/build/request.ts
Comment thread tests/build-ai-analyze.test.ts Outdated
The ai_analyzed column migration is now in its own PR so the DB schema
change can be reviewed and applied independently. This PR keeps the
types update (supabase.types.ts) since edge function tests reference
the new field name; the actual column is created by the migration in
the standalone migration PR.

Depends on #2285 being merged + the migration applied to
the shared DB before this PR's runtime path works.
riderx pushed a commit that referenced this pull request May 18, 2026
Adds a boolean column with default FALSE used by the upcoming AI build
analytics feature for one-analysis-per-job idempotency. Backward-compatible
schema change — existing rows get the default; no app code on main reads
or writes this column yet.

The application code that consumes this column lands in PR #2284 (AI build
analytics feature). This migration ships separately so the schema change
can be reviewed and applied independently from the larger feature.
…upabase

postAnalyzeRequest was hitting ${apiHost}/functions/v1/build/ai_analyze
(Supabase Edge Functions convention), but apiHost is the Capgo CF Workers
API gateway (api.capgo.app / api.preprod.capgo.app). Routes there are
mounted directly under the host — every other /build/* endpoint (start,
cancel, status, logs) uses just ${host}/build/... and that's what works
in preprod.

Verified against preprod:
  /functions/v1/build/ai_analyze  -> 404
  /build/ai_analyze               -> 401 (route exists, just needs auth)

Test updated to expect the correct URL.
… warning

Four small additions following preprod testing feedback:

1. Prompt-injection defense (matches capgo_builder change):
   - SYSTEM_PROMPT (kept in sync) gains a SECURITY section telling Kimi
     the user message is untrusted DATA inside <BUILD_LOG>...</BUILD_LOG>
     tags, not instructions.
   - writeLocalAiFile now uses the same boundary so users running the
     local-AI file against any LLM get the same protection.

2. Spinner during the Capgo AI request — wraps postAnalyzeRequest with
   @clack/prompts spinner so the user sees 'Analyzing build log…' instead
   of an unexplained pause. Skipped when stdout isn't a TTY (CI).

3. Tiny terminal markdown renderer (cli/src/ai/render-markdown.ts, no new
   dep, ~85 lines). Handles ### headers, fenced code blocks, **bold**,
   *italic*, `inline code`, numbered + bullet lists via raw ANSI escapes.
   Passes input through unchanged when stdout isn't a TTY so output stays
   pipeable. 8 unit tests.

4. 'AI can make mistakes — verify before applying fixes' warning printed
   after every analysis (both Capgo AI and Local AI flows).
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
cli/src/build/request.ts (1)

2000-2002: ⚡ Quick win

Use formatError(...) for this user-facing error output.

Line 2001 currently prints a raw error string; route it through the shared formatter for consistent CLI UX.

As per coding guidelines, "For user-visible error messages, format errors with formatError(...) instead of dumping raw exceptions when possible".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/build/request.ts` around lines 2000 - 2002, The catch block that
writes AI analysis errors to stderr currently prints a raw error string; replace
that raw output with the shared user-facing formatter by passing the caught
error into formatError(...) before writing to process.stderr. Locate the catch
handling in the AI analysis flow (the catch block containing
process.stderr.write(`AI analysis flow errored: ...`)) and change the message to
use formatError(err) (or formatError(err instanceof Error ? err : new
Error(String(err))) if needed) so the CLI displays the formatted error
consistently.
cli/test/test-ai-analyze-flow.mjs (1)

16-21: ⚡ Quick win

Prefer async/await in the test harness instead of .then() chaining.

Line 16-Line 21 can be rewritten as async/await to match repo standards and keep error flow clearer.

Proposed change
-function test(name, fn) {
-  return Promise.resolve()
-    .then(() => fn())
-    .then(() => { console.log(`✅ ${name}`); passed++ })
-    .catch((err) => { console.error(`❌ ${name}\n   ${err.message}`); failed++ })
+async function test(name, fn) {
+  try {
+    await fn()
+    console.log(`✅ ${name}`)
+    passed++
+  }
+  catch (err) {
+    console.error(`❌ ${name}\n   ${err.message}`)
+    failed++
+  }
 }

As per coding guidelines, "**/*.{ts,tsx,js}: Always use async/await instead of promises with .then() chains".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/test/test-ai-analyze-flow.mjs` around lines 16 - 21, The test harness
uses Promise.then() chaining in the test function; rewrite function test(name,
fn) as an async function using try/catch and await to call fn(), then increment
passed or failed and log results; keep the same messages (`✅ ${name}` and `❌
${name}\n   ${err.message}`) and the passed/failed increments so behavior is
unchanged while replacing the .then()/.catch() chain with async/await and
try/catch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cli/src/ai/render-markdown.ts`:
- Around line 57-75: The three regex-based matchers (headerMatch, numberedMatch,
bulletMatch) on the raw line can cause super-linear backtracking; replace them
with bounded parsing using cheap prefix checks and index/slice instead: for
headers, check if raw startsWith '#' then count leading '#' up to 6, verify next
char is space and slice the header text to produce the same text and level used
with stylize; for numbered lists, scan for leading whitespace, then parse
consecutive digits up to the first '.' (validate the '.' and following space) to
extract indent, number (n) and rest, then call renderInline/restyled number as
before; for bullets, detect leading whitespace then check next non-space char is
'-' or '*' and a following space, slice out the rest and handle as before; keep
using stylize, ANSI constants and renderInline to preserve behavior.
- Around line 34-38: The renderMarkdown function contains multiple lint/regex
issues: make the first regex in stylization (the pattern using *? at line with
replace) greedy by removing the useless lazy quantifier (change `*?` to `*`),
fix the if statement that returns early when !isTTY by adding a newline or
braces so the `if` has its own block, and refactor the three vulnerable regexes
(the patterns at/near lines 57, 66, 74 in render-markdown.ts) to eliminate
overlapping `\s+` with `.+`/`.*` (replace with negated character classes like
`[^\\s]+` or use atomic grouping/possessive constructs) to prevent super-linear
backtracking while preserving the intended matches; update the associated calls
in renderMarkdown/stylize accordingly.

---

Nitpick comments:
In `@cli/src/build/request.ts`:
- Around line 2000-2002: The catch block that writes AI analysis errors to
stderr currently prints a raw error string; replace that raw output with the
shared user-facing formatter by passing the caught error into formatError(...)
before writing to process.stderr. Locate the catch handling in the AI analysis
flow (the catch block containing process.stderr.write(`AI analysis flow errored:
...`)) and change the message to use formatError(err) (or formatError(err
instanceof Error ? err : new Error(String(err))) if needed) so the CLI displays
the formatted error consistently.

In `@cli/test/test-ai-analyze-flow.mjs`:
- Around line 16-21: The test harness uses Promise.then() chaining in the test
function; rewrite function test(name, fn) as an async function using try/catch
and await to call fn(), then increment passed or failed and log results; keep
the same messages (`✅ ${name}` and `❌ ${name}\n   ${err.message}`) and the
passed/failed increments so behavior is unchanged while replacing the
.then()/.catch() chain with async/await and try/catch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d81943f0-ed4e-4efe-b786-d4d8693ae7bf

📥 Commits

Reviewing files that changed from the base of the PR and between fa33046 and 89e8a30.

📒 Files selected for processing (7)
  • cli/package.json
  • cli/src/ai/analyze.ts
  • cli/src/ai/prompt.ts
  • cli/src/ai/render-markdown.ts
  • cli/src/build/request.ts
  • cli/test/test-ai-analyze-flow.mjs
  • cli/test/test-ai-render-markdown.mjs
🚧 Files skipped from review as they are similar to previous changes (3)
  • cli/src/ai/prompt.ts
  • cli/package.json
  • cli/src/ai/analyze.ts

Comment thread cli/src/ai/render-markdown.ts Outdated
Comment thread cli/src/ai/render-markdown.ts Outdated
Old: every code line in cyan with the literal triple-backtick fence lines
shown in gray — fence syntax leaked through and cyan content was harder
to read than default terminal text.

New: fence lines hidden entirely (the bar IS the 'this is code' signal);
each code line prefixed with '▎ ' in dim gray, content in default color.
Blank lines inside the block still get the bar so the left edge stays
unbroken. Mirrors git-diff / GitHub-review styling.

Updated tests cover both fence-hiding and the unbroken-bar invariant.
cli/src/build/request.ts (runCapgoAi auto-upload path)
  Silent catch hid genuine failures from users (e.g. log file missing
  because capture wasn't enabled, or /tmp permissions). Now write a
  concise diagnostic to stderr with the log path and the underlying
  error message before returning.

tests/build-ai-analyze.test.ts
  ESLint auto-fix only — splits chained .rejects.toThrow across lines
  (antfu/consistent-chaining), adds newline after multi-line if bodies
  (antfu/if-newline), changes inline-object-type delimiters from ';' to
  ',' (style/member-delimiter-style), and adds the required trailing
  comma in the multi-arg Response constructor. No behavioural change.

Skipped: coderabbit's nitpick to convert it(...) to it.concurrent(...).
The suite finishes in ~250ms; the speedup isn't worth the mock-isolation
risk the nitpick itself flags, and beforeEach already uses mockReset()
which would need a careful audit to confirm safe under concurrent runs.
5 lint errors flagged by regexp/no-super-linear-backtracking and friends:

1. Removed useless lazy quantifier in *italic* regex (`[^*]*?` →
   `[^*]*` — `[^*]` already excludes the closing `*`, so lazy/greedy
   makes no difference) — auto-fix.
2. `if (!isTTY) return` split onto two lines (antfu/if-newline) — auto-fix.
3-5. Header/numbered-list/bullet-list regexes used `\s+` which overlaps
   with the captured `.+`/`.*` rest, allowing polynomial backtracking
   on input like `### " + ' '*1000`. Switched to literal ` +` for the
   separator and anchored the captured rest with `\S` so the engine can't
   backtrack into the spaces. Real markdown headers/list items always have
   a non-space character after the marker anyway.

All 9 renderer tests still pass; lint now clean on the file.
…now)

The bidirectional check needed cross-repo read access — feasible for the
public capgo repo (anonymous raw URL works) but required a PAT secret
(GH_TOKEN_READ_BUILDER) for the private capgo_builder side. Not worth
the secret-provisioning overhead.

The prompt files already document capgo_builder as the source of truth.
The remaining check on the capgo_builder side catches drift whenever the
worker prompt is edited; drift introduced by editing the CLI mirror first
gets caught the next time anything touches the worker prompt — close
enough for v1.

If you want stricter protection later, options:
- Make the worker prompt the literal source via a generate-mirror script
- Use a GitHub App with cross-repo read scope
- Vendor the prompt as a tiny shared npm package
@sonarqubecloud
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants