Repository navigation
fix(review): an honest PR comment: real lint-only banner, AI lane status, PR intent, project_dir - #1720
fix(review): an honest PR comment: real lint-only banner, AI lane status, PR intent, project_dir#1720RaghwendraSingh wants to merge 1 commit into
Conversation
…tus, PR intent, `project_dir` - Lint-only now means no changed model resolved against a manifest; an undecidable finding no longer flips the whole run. Undecidable findings, unverified models, out-of-scope files and AI-lane problems each get their own line in the summary. - The advisory AI lane reports `ok | skipped | timeout | error` with a reason, model and duration on the signed envelope (`ai`) and in the comment; it never fails silently. The timeout scales with prompt size (60 s + 1 s per 2K chars, capped at 5 min). `--ai-model provider/model` selects the model explicitly. - The CLI passes the PR title and description to the intent check (GitHub `pull_request` event, GitLab `CI_MERGE_REQUEST_TITLE`/`_DESCRIPTION`). - The action gains `project_dir` (→ `--cwd`) for monorepos. - Docs: what each input unlocks (fetch depth, manifest, compiled SQL, `target-base/`, `catalog.json`) and how to compile the base. - `review_run` telemetry adds `undecided`, `unverified_models`, `ai_state`, `ai_duration_ms`, `base_source`, `out_of_scope_files`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the
|
|
Thanks for your contribution! This PR doesn't have a linked issue. All PRs must reference an existing issue. Please:
See CONTRIBUTING.md for details. |
There was a problem hiding this comment.
9 issues found across 11 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/opencode/src/altimate/telemetry/index.ts">
<violation number="1" location="packages/opencode/src/altimate/telemetry/index.ts:1606">
P3: `ai_state` is typed as `string`, but the AI lane only produces `ok | skipped | timeout | error`. Narrow the type to that union so the telemetry contract matches the envelope schema and typos are caught at compile time.</violation>
</file>
<file name="packages/opencode/test/altimate/review-ci-honesty.test.ts">
<violation number="1" location="packages/opencode/test/altimate/review-ci-honesty.test.ts:157">
P3: This test creates a temp directory and never removes it, so every run leaves a `review-event-*` directory behind. Remove it in a `finally` block (`fs.rm(dir, { recursive: true, force: true })`) or use the repo's tmpdir fixture with auto cleanup.</violation>
</file>
<file name="docs/docs/usage/dbt-pr-review.md">
<violation number="1" location="docs/docs/usage/dbt-pr-review.md:218">
P2: In a monorepo the base-compile example writes `target-base/` at the repo root and runs `dbt` from the repo root, but the reviewer reads `target-base/` relative to `project_dir`. Equivalence and column-breakage checks would silently fall back to not verified. Note that the base compile must target `<project_dir>/target-base` and run inside the project folder, or the Monorepos paragraph should say so.</violation>
</file>
<file name="packages/opencode/src/altimate/review/ai-review.ts">
<violation number="1" location="packages/opencode/src/altimate/review/ai-review.ts:66">
P2: `safeReason` still exposes credential formats that do not use the four dashed prefixes, so provider failures can disclose secrets in PR comments. Reuse the repository's credential/error redactor or mask credential-labeled values and credential-bearing URLs before truncating the reason.
(Based on your team's feedback about masking credential-stem error values.) [a5c5a10f-6a4a-4e42-8c2e-f8e01b48683d]</violation>
</file>
<file name="packages/opencode/src/altimate/review/orchestrate.ts">
<violation number="1" location="packages/opencode/src/altimate/review/orchestrate.ts:1418">
P2: Empty-scope reviews now emit an AI `skipped` status and coverage note even though there is no review scope or AI lane to skip. Guard the AI-status block on a non-empty review scope so empty reviews leave `ai` undefined.</violation>
<violation number="2" location="packages/opencode/src/altimate/review/orchestrate.ts:1444">
P2: AI callback failures bypass the redaction promised by `AiLaneStatus` and can publish raw error text in the signed envelope and PR comment. Apply the same credential-safe error formatter before storing `ai.reason`.
(Based on your team's feedback about masking credential-bearing error text.) [173edeff-f795-42ca-9d7d-86524f63e58a]</violation>
</file>
<file name="github/review/action.yml">
<violation number="1" location="github/review/action.yml:189">
P2: A misspelled `project_dir` silently broadens a monorepo review to every changed file. Validate that the directory contains `dbt_project.yml`/`.yaml` before adding `--cwd`, or make the review fail closed when project discovery fails.</violation>
</file>
<file name="packages/opencode/src/altimate/review/format.ts">
<violation number="1" location="packages/opencode/src/altimate/review/format.ts:136">
P2: Mixed head/base compile failures are rendered with the first model’s reason for every model, which misstates review coverage. Group the entries by reason or render each model with its own reason.</violation>
<violation number="2" location="packages/opencode/src/altimate/review/format.ts:144">
P2: The PR comment does not report the AI lane’s available model and duration: successful runs show only the model, while failed or timed-out runs show only state and reason. Include model and duration in the rendered AI status.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
| - name: Compile the base into target-base/ | ||
| run: | | ||
| git worktree add ../base "origin/${{ github.base_ref }}" | ||
| (cd ../base && dbt deps && dbt compile --target-path "$GITHUB_WORKSPACE/target-base") |
There was a problem hiding this comment.
P2: In a monorepo the base-compile example writes target-base/ at the repo root and runs dbt from the repo root, but the reviewer reads target-base/ relative to project_dir. Equivalence and column-breakage checks would silently fall back to not verified. Note that the base compile must target <project_dir>/target-base and run inside the project folder, or the Monorepos paragraph should say so.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At docs/docs/usage/dbt-pr-review.md, line 218:
<comment>In a monorepo the base-compile example writes `target-base/` at the repo root and runs `dbt` from the repo root, but the reviewer reads `target-base/` relative to `project_dir`. Equivalence and column-breakage checks would silently fall back to not verified. Note that the base compile must target `<project_dir>/target-base` and run inside the project folder, or the Monorepos paragraph should say so.</comment>
<file context>
@@ -199,6 +199,34 @@ jobs:
+ - name: Compile the base into target-base/
+ run: |
+ git worktree add ../base "origin/${{ github.base_ref }}"
+ (cd ../base && dbt deps && dbt compile --target-path "$GITHUB_WORKSPACE/target-base")
+```
+
</file context>
| return raw | ||
| .replace(/\s+/g, " ") | ||
| .replace(/(bearer\s+)[^\s]+/gi, "$1***") | ||
| .replace(/\b(sk|pk|key|token)[-_][A-Za-z0-9_-]{8,}/gi, "***") |
There was a problem hiding this comment.
P2: safeReason still exposes credential formats that do not use the four dashed prefixes, so provider failures can disclose secrets in PR comments. Reuse the repository's credential/error redactor or mask credential-labeled values and credential-bearing URLs before truncating the reason.
(Based on your team's feedback about masking credential-stem error values.) [a5c5a10f-6a4a-4e42-8c2e-f8e01b48683d]
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/opencode/src/altimate/review/ai-review.ts, line 66:
<comment>`safeReason` still exposes credential formats that do not use the four dashed prefixes, so provider failures can disclose secrets in PR comments. Reuse the repository's credential/error redactor or mask credential-labeled values and credential-bearing URLs before truncating the reason.
(Based on your team's feedback about masking credential-stem error values.) [a5c5a10f-6a4a-4e42-8c2e-f8e01b48683d]</comment>
<file context>
@@ -31,6 +32,39 @@ export interface AiReviewInput {
+ return raw
+ .replace(/\s+/g, " ")
+ .replace(/(bearer\s+)[^\s]+/gi, "$1***")
+ .replace(/\b(sk|pk|key|token)[-_][A-Za-z0-9_-]{8,}/gi, "***")
+ .slice(0, 200)
}
</file context>
| // headless-without-model) — the review degrades to deterministic-only. | ||
| if (lanes.has("ai_review") && input.aiReview) { | ||
| let aiStatus: AiLaneStatus | undefined | ||
| if (!lanes.has("ai_review")) { |
There was a problem hiding this comment.
P2: Empty-scope reviews now emit an AI skipped status and coverage note even though there is no review scope or AI lane to skip. Guard the AI-status block on a non-empty review scope so empty reviews leave ai undefined.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/opencode/src/altimate/review/orchestrate.ts, line 1418:
<comment>Empty-scope reviews now emit an AI `skipped` status and coverage note even though there is no review scope or AI lane to skip. Guard the AI-status block on a non-empty review scope so empty reviews leave `ai` undefined.</comment>
<file context>
@@ -1410,7 +1414,12 @@ export async function runReview(input: OrchestrateInput): Promise<VerdictEnvelop
// headless-without-model) — the review degrades to deterministic-only.
- if (lanes.has("ai_review") && input.aiReview) {
+ let aiStatus: AiLaneStatus | undefined
+ if (!lanes.has("ai_review")) {
+ aiStatus = { state: "skipped", reason: `not part of the ${tier} tier` }
+ } else if (!input.aiReview) {
</file context>
| // A failed AI lane must never fail the review. | ||
| } catch (err) { | ||
| // A failed AI lane must never fail the review — but it must say so. | ||
| aiStatus = { state: "error", reason: String((err as Error)?.message ?? err).slice(0, 200) } |
There was a problem hiding this comment.
P2: AI callback failures bypass the redaction promised by AiLaneStatus and can publish raw error text in the signed envelope and PR comment. Apply the same credential-safe error formatter before storing ai.reason.
(Based on your team's feedback about masking credential-bearing error text.) [173edeff-f795-42ca-9d7d-86524f63e58a]
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/opencode/src/altimate/review/orchestrate.ts, line 1444:
<comment>AI callback failures bypass the redaction promised by `AiLaneStatus` and can publish raw error text in the signed envelope and PR comment. Apply the same credential-safe error formatter before storing `ai.reason`.
(Based on your team's feedback about masking credential-bearing error text.) [173edeff-f795-42ca-9d7d-86524f63e58a]</comment>
<file context>
@@ -1419,16 +1428,20 @@ export async function runReview(input: OrchestrateInput): Promise<VerdictEnvelop
- // A failed AI lane must never fail the review.
+ } catch (err) {
+ // A failed AI lane must never fail the review — but it must say so.
+ aiStatus = { state: "error", reason: String((err as Error)?.message ?? err).slice(0, 200) }
}
}
</file context>
| ALTIMATE_REVIEW_SIGNING_KEY: ${{ inputs.signing_key }} | ||
| run: | | ||
| args=(--mode "$IN_MODE" --manifest "$IN_MANIFEST" --severity "$IN_SEVERITY") | ||
| [[ -n "$IN_PROJECT_DIR" && "$IN_PROJECT_DIR" != "." ]] && args+=(--cwd "$IN_PROJECT_DIR") |
There was a problem hiding this comment.
P2: A misspelled project_dir silently broadens a monorepo review to every changed file. Validate that the directory contains dbt_project.yml/.yaml before adding --cwd, or make the review fail closed when project discovery fails.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At github/review/action.yml, line 189:
<comment>A misspelled `project_dir` silently broadens a monorepo review to every changed file. Validate that the directory contains `dbt_project.yml`/`.yaml` before adding `--cwd`, or make the review fail closed when project discovery fails.</comment>
<file context>
@@ -181,6 +186,7 @@ runs:
ALTIMATE_REVIEW_SIGNING_KEY: ${{ inputs.signing_key }}
run: |
args=(--mode "$IN_MODE" --manifest "$IN_MANIFEST" --severity "$IN_SEVERITY")
+ [[ -n "$IN_PROJECT_DIR" && "$IN_PROJECT_DIR" != "." ]] && args+=(--cwd "$IN_PROJECT_DIR")
[[ -n "$IN_BASE" ]] && args+=(--base "$IN_BASE")
[[ -n "$IN_HEAD" ]] && args+=(--head "$IN_HEAD")
</file context>
| [[ -n "$IN_PROJECT_DIR" && "$IN_PROJECT_DIR" != "." ]] && args+=(--cwd "$IN_PROJECT_DIR") | |
| if [[ -n "$IN_PROJECT_DIR" && "$IN_PROJECT_DIR" != "." ]]; then | |
| project_path="${GITHUB_WORKSPACE:-$PWD}/$IN_PROJECT_DIR" | |
| if [[ ! -f "$project_path/dbt_project.yml" && ! -f "$project_path/dbt_project.yaml" ]]; then | |
| echo "::error::project_dir must contain dbt_project.yml or dbt_project.yaml" | |
| exit 1 | |
| fi | |
| args+=(--cwd "$IN_PROJECT_DIR") | |
| fi |
| notes.push(`${env.scope.outOfScopeFiles} changed file(s) outside the dbt project were not reviewed.`) | ||
| if (env.ai && env.ai.state !== "ok") { | ||
| const what = { skipped: "skipped", timeout: "timed out", error: "failed" }[env.ai.state] | ||
| notes.push(`Advisory AI review ${what}${env.ai.reason ? `: ${env.ai.reason}` : ""}.`) |
There was a problem hiding this comment.
P2: The PR comment does not report the AI lane’s available model and duration: successful runs show only the model, while failed or timed-out runs show only state and reason. Include model and duration in the rendered AI status.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/opencode/src/altimate/review/format.ts, line 144:
<comment>The PR comment does not report the AI lane’s available model and duration: successful runs show only the model, while failed or timed-out runs show only state and reason. Include model and duration in the rendered AI status.</comment>
<file context>
@@ -110,11 +111,41 @@ export function renderSummary(env: VerdictEnvelope): string {
+ notes.push(`${env.scope.outOfScopeFiles} changed file(s) outside the dbt project were not reviewed.`)
+ if (env.ai && env.ai.state !== "ok") {
+ const what = { skipped: "skipped", timeout: "timed out", error: "failed" }[env.ai.state]
+ notes.push(`Advisory AI review ${what}${env.ai.reason ? `: ${env.ai.reason}` : ""}.`)
+ }
+ return notes.length ? [...notes.map((n) => `> ℹ️ ${n}`), ""] : []
</file context>
| const head = unverified.slice(0, 5).map((u) => `\`${u.path}\``).join(", ") | ||
| const more = unverified.length > 5 ? ` and ${unverified.length - 5} more` : "" | ||
| notes.push( | ||
| `${unverified.length} model(s) not verified by the engine — ${unverified[0].reason}: ${head}${more}. ` + |
There was a problem hiding this comment.
P2: Mixed head/base compile failures are rendered with the first model’s reason for every model, which misstates review coverage. Group the entries by reason or render each model with its own reason.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/opencode/src/altimate/review/format.ts, line 136:
<comment>Mixed head/base compile failures are rendered with the first model’s reason for every model, which misstates review coverage. Group the entries by reason or render each model with its own reason.</comment>
<file context>
@@ -110,11 +111,41 @@ export function renderSummary(env: VerdictEnvelope): string {
+ const head = unverified.slice(0, 5).map((u) => `\`${u.path}\``).join(", ")
+ const more = unverified.length > 5 ? ` and ${unverified.length - 5} more` : ""
+ notes.push(
+ `${unverified.length} model(s) not verified by the engine — ${unverified[0].reason}: ${head}${more}. ` +
+ "Run `dbt compile` (and compile the base into `target-base/`) for equivalence and lineage checks.",
+ )
</file context>
| /** Models the engine could not verify (no compiled SQL). */ | ||
| unverified_models?: number | ||
| /** Advisory AI lane outcome: ok | skipped | timeout | error. */ | ||
| ai_state?: string |
There was a problem hiding this comment.
P3: ai_state is typed as string, but the AI lane only produces ok | skipped | timeout | error. Narrow the type to that union so the telemetry contract matches the envelope schema and typos are caught at compile time.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/opencode/src/altimate/telemetry/index.ts, line 1606:
<comment>`ai_state` is typed as `string`, but the AI lane only produces `ok | skipped | timeout | error`. Narrow the type to that union so the telemetry contract matches the envelope schema and typos are caught at compile time.</comment>
<file context>
@@ -1594,10 +1594,20 @@ export namespace Telemetry {
+ /** Models the engine could not verify (no compiled SQL). */
+ unverified_models?: number
+ /** Advisory AI lane outcome: ok | skipped | timeout | error. */
+ ai_state?: string
+ ai_duration_ms?: number
+ /** Where the review base came from (flag, github_event, default_branch, …). */
</file context>
| ai_state?: string | |
| ai_state?: "ok" | "skipped" | "timeout" | "error" |
|
|
||
| describe("PR title and description reach the intent check", () => { | ||
| test("GitHub: read from the pull_request event", async () => { | ||
| const dir = await fs.mkdtemp(path.join(tmpdir(), "review-event-")) |
There was a problem hiding this comment.
P3: This test creates a temp directory and never removes it, so every run leaves a review-event-* directory behind. Remove it in a finally block (fs.rm(dir, { recursive: true, force: true })) or use the repo's tmpdir fixture with auto cleanup.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/opencode/test/altimate/review-ci-honesty.test.ts, line 157:
<comment>This test creates a temp directory and never removes it, so every run leaves a `review-event-*` directory behind. Remove it in a `finally` block (`fs.rm(dir, { recursive: true, force: true })`) or use the repo's tmpdir fixture with auto cleanup.</comment>
<file context>
@@ -0,0 +1,188 @@
+
+describe("PR title and description reach the intent check", () => {
+ test("GitHub: read from the pull_request event", async () => {
+ const dir = await fs.mkdtemp(path.join(tmpdir(), "review-event-"))
+ const ev = path.join(dir, "event.json")
+ await fs.writeFile(ev, JSON.stringify({ pull_request: { title: "Add fct_orders", body: "## Spec\n- grain: order_id" } }))
</file context>
| if (env.summary.degraded) { | ||
| lines.push( | ||
| "> ⚙️ **Lint-only run** — no dbt manifest/warehouse was available, so lineage, equivalence and", | ||
| "> ⚙️ **Lint-only run** — no changed model resolved against a dbt manifest, so lineage, equivalence and", |
There was a problem hiding this comment.
WARNING: The banner now asserts a cause that is only one of the two ways summary.degraded becomes true.
orchestrate.ts:1120 sets runDegraded = modelFiles.length > 0 ? !anyManifest : reviewable.length === 0, so an empty reviewable set also renders this banner. That happens for a PR whose only changed files are non-dbt (.md, .json, … are dropped by filterChangedFiles → shouldReview), and there the advice "Wire manifest_path" cannot change anything. The comment then misstates why checks were skipped, which is the class of problem this PR is fixing. Branch the wording on the actual cause, or give the empty-reviewable case its own note.
| const head = unverified.slice(0, 5).map((u) => `\`${u.path}\``).join(", ") | ||
| const more = unverified.length > 5 ? ` and ${unverified.length - 5} more` : "" | ||
| notes.push( | ||
| `${unverified.length} model(s) not verified by the engine — ${unverified[0].reason}: ${head}${more}. ` + |
There was a problem hiding this comment.
SUGGESTION: Only the first model's reason is reported for the whole set.
unverified entries carry two distinct reasons (orchestrate.ts:1108 head-side vs 1110 base-side), so a PR that mixes gaps gets every path labelled with unverified[0].reason. With unverified[0] a base-side entry, the line even tells the user to run dbt compile for models that were only missing target-base. Group by reason or use a neutral phrase ("no compiled SQL for the head or base").
| notes.push(`${env.scope.outOfScopeFiles} changed file(s) outside the dbt project were not reviewed.`) | ||
| if (env.ai && env.ai.state !== "ok") { | ||
| const what = { skipped: "skipped", timeout: "timed out", error: "failed" }[env.ai.state] | ||
| notes.push(`Advisory AI review ${what}${env.ai.reason ? `: ${env.ai.reason}` : ""}.`) |
There was a problem hiding this comment.
SUGGESTION: env.ai.reason is interpolated into a > ℹ️ blockquote verbatim.
safeReason in ai-review.ts collapses whitespace, but the orchestrate catch path does not (see the comment there), so a multi-line error message breaks out of the blockquote and can inject arbitrary markdown into the posted PR comment. The footer at lines 114-115 has the same shape: env.scope.baseRef and env.ai.model are placed in single-backtick spans without the fence-length logic this file already applies to tierReasons (lines 64-74, added for exactly this reason). Reuse that escaping for these values.
| if (lanes.has("ai_review") && input.aiReview) { | ||
| let aiStatus: AiLaneStatus | undefined | ||
| if (!lanes.has("ai_review")) { | ||
| aiStatus = { state: "skipped", reason: `not part of the ${tier} tier` } |
There was a problem hiding this comment.
SUGGESTION: This reason can be false.
lanes is input.config.reviewers.length ? input.config.reviewers : TIER_LANES[tier] (line 1175), so the branch also fires when a user restricts lanes with reviewers: in .altimate/review.yml. A full-tier run with reviewers: ["sql_quality"] then reports "not part of the full tier" in the PR comment, which is a misstatement of the same kind the PR set out to remove. Distinguish "excluded by the reviewers config" from "not part of the tier".
| // A failed AI lane must never fail the review. | ||
| } catch (err) { | ||
| // A failed AI lane must never fail the review — but it must say so. | ||
| aiStatus = { state: "error", reason: String((err as Error)?.message ?? err).slice(0, 200) } |
There was a problem hiding this comment.
SUGGESTION: This reason reaches the signed envelope and the PR comment unmasked.
Unlike safeReason in ai-review.ts, it is not whitespace-collapsed, not credential-masked, and only sliced to 200 chars — while AiLaneStatus.reason is documented as "never contains credentials". runAiReview does not throw today, so this is the fallback for any injected lane, but the contract should hold on every path: reuse the masking helper (or sanitize the message here) rather than embedding the raw error.
| - name: Compile the base into target-base/ | ||
| run: | | ||
| git worktree add ../base "origin/${{ github.base_ref }}" | ||
| (cd ../base && dbt deps && dbt compile --target-path "$GITHUB_WORKSPACE/target-base") |
There was a problem hiding this comment.
SUGGESTION: This snippet does not work in the monorepo layout the paragraph below documents.
It writes target-base/ to the repository root and runs dbt compile from the worktree root. The resolver reads <dbt root>/target-base/compiled/<project>/… (compiled.ts:117, with opts.cwd = dbtRoot from run.ts:363), and dbt compile needs dbt_project.yml, so with project_dir: dbt both steps fail or land where the review never looks — equivalence and column-breakage silently stay off. Add a monorepo variant, e.g. (cd ../base/dbt && dbt deps && dbt compile --target-path "$GITHUB_WORKSPACE/dbt/target-base"), and note the path must sit next to the project's manifest.json.
|
|
||
| describe("PR title and description reach the intent check", () => { | ||
| test("GitHub: read from the pull_request event", async () => { | ||
| const dir = await fs.mkdtemp(path.join(tmpdir(), "review-event-")) |
There was a problem hiding this comment.
SUGGESTION: This temp directory is never removed.
test/AGENTS.md ("Temporary Directory Fixture") asks tests to use tmpdir() / tmpdirScoped() from fixture/fixture.ts so cleanup is automatic; a raw fs.mkdtemp leaks one directory per run under the parallel suite. Use await using tmp = await tmpdir({ init: ... }) and write event.json inside tmp.path.
Code Review SummaryStatus: 7 Issues Found | Recommendation: Address before merge Overview
Fix these issues in Kilo Cloud Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (11 files)
Verified on Reviewed by deepseek-v4.1-flash-1017 · Input: 148.1K · Output: 44.8K · Cached: 4.2M Review guidance: REVIEW.md from base branch |
dev-punia-altimate
left a comment
There was a problem hiding this comment.
🤖 Code Review — OpenCodeReview (Gemini) — 3 finding(s)
- 3 anchored to a line (posted inline when the comment stream is on)
- 0 without a line anchor
All findings (full text)
1. packages/opencode/src/altimate/review/run.ts (L382-L385)
[🔵 LOW] The checklist prohibits the use of nested ternary expressions. Please refactor this to avoid nesting, perhaps by computing the disabled reason in a separate variable before using it here.
Suggested change:
cliVersion: opts.cliVersion ?? Installation.VERSION,
aiReview: opts.noAi || config.ai === false ? undefined : runAiReview,
aiDisabledReason: (function() {
if (opts.noAi) return "disabled by --no-ai"
if (config.ai === false) return "disabled by `ai: false` in .altimate/review.yml"
return undefined
})(),
aiModel: opts.aiModel,
2. packages/opencode/src/altimate/review/ai-review.ts (L124-L129)
[🔴 HIGH] Moving buildUserMessage outside the try...catch block introduces the risk of unhandled exceptions. If buildUserMessage throws an error (e.g., due to an unexpected value or formatting error), it will crash the function, violating the JSDoc contract that "a review must not crash because the AI layer is unavailable".
Consider moving the message generation and timeout setup inside the try block to ensure all potential errors are caught and a proper failure status is returned.
Suggested change:
const controller = new AbortController()
let timeout: ReturnType<typeof setTimeout> | undefined
let timeoutMs = 0
let modelLabel: string | undefined
try {
const message = buildUserMessage({ ...input, files })
timeoutMs = aiTimeoutMs(message.length)
timeout = setTimeout(() => controller.abort(), timeoutMs)
3. packages/opencode/src/altimate/review/ai-review.ts (L61-L68)
[🟠 MEDIUM] While this effectively sanitizes bearer tokens and typical API key patterns, it may not cover other sensitive credentials occasionally found in network errors, such as Basic <base64> auth headers or credentials embedded within URLs (e.g., https://user:pass@host/...).
Consider expanding the regex to cover these patterns for more robust redaction.
Suggested change:
function safeReason(err: unknown): string {
const raw = err instanceof Error ? err.message : String(err)
return raw
.replace(/\s+/g, " ")
.replace(/(bearer\s+|basic\s+)[^\s]+/gi, "$1***")
.replace(/:\/\/[^/]*@[^/]+/gi, "://***@")
.replace(/\b(sk|pk|key|token)[-_][A-Za-z0-9_-]{8,}/gi, "***")
.slice(0, 200)
}
| cliVersion: opts.cliVersion ?? Installation.VERSION, | ||
| aiReview: opts.noAi || config.ai === false ? undefined : runAiReview, | ||
| aiDisabledReason: opts.noAi ? "disabled by --no-ai" : config.ai === false ? "disabled by `ai: false` in .altimate/review.yml" : undefined, | ||
| aiModel: opts.aiModel, |
There was a problem hiding this comment.
[🔵 LOW] The checklist prohibits the use of nested ternary expressions. Please refactor this to avoid nesting, perhaps by computing the disabled reason in a separate variable before using it here.
Suggested change:
| cliVersion: opts.cliVersion ?? Installation.VERSION, | |
| aiReview: opts.noAi || config.ai === false ? undefined : runAiReview, | |
| aiDisabledReason: opts.noAi ? "disabled by --no-ai" : config.ai === false ? "disabled by `ai: false` in .altimate/review.yml" : undefined, | |
| aiModel: opts.aiModel, | |
| cliVersion: opts.cliVersion ?? Installation.VERSION, | |
| aiReview: opts.noAi || config.ai === false ? undefined : runAiReview, | |
| aiDisabledReason: (function() { | |
| if (opts.noAi) return "disabled by --no-ai" | |
| if (config.ai === false) return "disabled by `ai: false` in .altimate/review.yml" | |
| return undefined | |
| })(), | |
| aiModel: opts.aiModel, |
| const message = buildUserMessage({ ...input, files }) | ||
| const controller = new AbortController() | ||
| const timeout = setTimeout(() => controller.abort(), AI_TIMEOUT_MS) | ||
| const timeoutMs = aiTimeoutMs(message.length) | ||
| const timeout = setTimeout(() => controller.abort(), timeoutMs) | ||
| let modelLabel: string | undefined | ||
| try { |
There was a problem hiding this comment.
[🔴 HIGH] Moving buildUserMessage outside the try...catch block introduces the risk of unhandled exceptions. If buildUserMessage throws an error (e.g., due to an unexpected value or formatting error), it will crash the function, violating the JSDoc contract that "a review must not crash because the AI layer is unavailable".
Consider moving the message generation and timeout setup inside the try block to ensure all potential errors are caught and a proper failure status is returned.
Suggested change:
| const message = buildUserMessage({ ...input, files }) | |
| const controller = new AbortController() | |
| const timeout = setTimeout(() => controller.abort(), AI_TIMEOUT_MS) | |
| const timeoutMs = aiTimeoutMs(message.length) | |
| const timeout = setTimeout(() => controller.abort(), timeoutMs) | |
| let modelLabel: string | undefined | |
| try { | |
| const controller = new AbortController() | |
| let timeout: ReturnType<typeof setTimeout> | undefined | |
| let timeoutMs = 0 | |
| let modelLabel: string | undefined | |
| try { | |
| const message = buildUserMessage({ ...input, files }) | |
| timeoutMs = aiTimeoutMs(message.length) | |
| timeout = setTimeout(() => controller.abort(), timeoutMs) |
| function safeReason(err: unknown): string { | ||
| const raw = err instanceof Error ? err.message : String(err) | ||
| return raw | ||
| .replace(/\s+/g, " ") | ||
| .replace(/(bearer\s+)[^\s]+/gi, "$1***") | ||
| .replace(/\b(sk|pk|key|token)[-_][A-Za-z0-9_-]{8,}/gi, "***") | ||
| .slice(0, 200) | ||
| } |
There was a problem hiding this comment.
[🟠 MEDIUM] While this effectively sanitizes bearer tokens and typical API key patterns, it may not cover other sensitive credentials occasionally found in network errors, such as Basic <base64> auth headers or credentials embedded within URLs (e.g., https://user:pass@host/...).
Consider expanding the regex to cover these patterns for more robust redaction.
Suggested change:
| function safeReason(err: unknown): string { | |
| const raw = err instanceof Error ? err.message : String(err) | |
| return raw | |
| .replace(/\s+/g, " ") | |
| .replace(/(bearer\s+)[^\s]+/gi, "$1***") | |
| .replace(/\b(sk|pk|key|token)[-_][A-Za-z0-9_-]{8,}/gi, "***") | |
| .slice(0, 200) | |
| } | |
| function safeReason(err: unknown): string { | |
| const raw = err instanceof Error ? err.message : String(err) | |
| return raw | |
| .replace(/\s+/g, " ") | |
| .replace(/(bearer\s+|basic\s+)[^\s]+/gi, "$1***") | |
| .replace(/:\/\/[^/]*@[^/]+/gi, "://***@") | |
| .replace(/\b(sk|pk|key|token)[-_][A-Za-z0-9_-]{8,}/gi, "***") | |
| .slice(0, 200) | |
| } |
🤖 Code Review — OpenCodeReview (Gemini) — 3 finding(s)
All findings (full text)1.
|
Issue for this PR
Closes #
Stacked on #1719; please review and merge that one first. Takes ideas from the closed #1241, not its code.
Type of change
What does this PR do?
The review comment could misstate what it checked:
degraded, so every manifest-backed run said "Lint-only run — no dbt manifest". Now it shows only when no changed model resolved against a manifest. Undecidable findings, models without compiled SQL, and files outside the project each get their own line.[]on every failure, so an expired key, a timeout, or a missing model looked like "no AI findings". It now reportsok | skipped | timeout | errorwith a reason (credentials masked), the model, and duration. These land in a new signedaifield and in the comment. The timeout now scales with prompt size (60 s + 1 s per 2K chars, capped at 5 min) instead of a fixed 60 s.--ai-model provider/modelpicks the model explicitly.prTitle/prBodyexisted but nothing passed them. The CLI now reads them from the GitHubpull_requestevent, or from GitLab'sCI_MERGE_REQUEST_TITLE/_DESCRIPTION.project_dirinput, mapped to--cwd, for monorepos.target-base/,catalog.json) and a snippet for compiling the base.review_rungainsundecided,unverified_models,ai_state,ai_duration_ms,base_source,out_of_scope_files.Open question, deliberately not changed:
--no-aistill auto-registers the free tier, which is a network call. A release test asserts that on purpose, so the decision is the maintainers'. Changing it is a one-line guard.How did you verify your code works?
test/altimate/review-ci-honesty.test.ts(13 tests):bun typecheckis clean.Cost side effects
None. The AI lane's timeout ceiling can rise from 60 s to 5 min on very large prompts, but the number of model calls doesn't change (still at most one per review).
Screenshots / recordings
N/A (CLI).
Checklist
🤖 Generated with Claude Code
Summary by cubic
Makes the dbt PR review comment candid about what it checked. Previously a single undecidable finding labeled any manifest-backed run as "Lint-only", and the advisory AI lane returned
[]on every failure, so an expired key, timeout, or missing model looked like "no AI findings".Review honesty
ok | skipped | timeout | errorwith a masked reason, model, and duration, on a new signedaienvelope field and in the comment; it never fails silently.--ai-model provider/modelselects the model explicitly.pull_requestevent or GitLab'sCI_MERGE_REQUEST_TITLE/_DESCRIPTION.Inputs, docs, and telemetry
project_dirinput maps to--cwdfor monorepos.review_runtelemetry gainsundecided,unverified_models,ai_state,ai_duration_ms,base_source, andout_of_scope_files.target-base/.Deliberately unchanged:
--no-aistill auto-registers the free tier, which is a network call; a release test asserts that, so the decision is the maintainers'. Model call count is unchanged (still at most one per review); only the timeout ceiling rises, to 5 minutes on very large prompts.Written for commit 1fda92a. Summary will update on new commits.