Skip to content

fix: improve tool reliability — sql_execute crash, edit context drift, webfetch failures#474

Open
anandgupta42 wants to merge 3 commits intomainfrom
fix/469-470-471-473-tool-reliability
Open

fix: improve tool reliability — sql_execute crash, edit context drift, webfetch failures#474
anandgupta42 wants to merge 3 commits intomainfrom
fix/469-470-471-473-tool-reliability

Conversation

@anandgupta42
Copy link
Contributor

@anandgupta42 anandgupta42 commented Mar 26, 2026

What does this PR do?

Fixes 4 issues that collectively caused 1,206 tool failures per day:

Issue Tool Failures/day Fix
#469 sql_execute 36 Safe napi import + regex fallback classifier
#470 edit 236 Closest-match snippet in error message
#471 webfetch 934 URL validation, 404 cache, actionable errors
#473 deps Bump yaml 2.8.2 → 2.8.3 (stack overflow fix)

#469sql_execute: getStatementTypes is not a function

  • Root cause: require("@altimateai/altimate-core") with any type and no guard. When napi binary fails to load, core.getStatementTypes is undefined.
  • Fix: Safe import with typeof check, regex fallback classifier, null input guards.

#470edit: "Could not find oldString" (context drift)

  • Root cause: Error message gives zero context — model retries with same stale oldString.
  • Fix: buildNotFoundMessage() uses Levenshtein to find closest match, shows 5-line snippet with line numbers, tells model to re-read the file.

#471webfetch: 934 failures (55% of all tool failures)

  • Root cause: No URL validation, no failure caching, generic error messages.
  • Fix: URL constructor validation, session-level 404/410/451 cache (5-min TTL), HTTP-status-specific error messages ("Do NOT retry" for 404, "Rate limited" with Retry-After for 429).

#473yaml 2.8.2 → 2.8.3

  • Stack overflow fix during node composition.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Issue for this PR

Closes #469, Closes #470, Closes #471, Closes #473

How did you verify your code works?

  • 156 tests pass across 5 test files (80 new tests)
  • sql-classify-fallback.test.ts: 42 tests — regex fallback parity, ReDoS protection, null inputs, bypass attempts
  • edit-error-context.test.ts: 14 tests — Levenshtein matching, snippet bounds, edge cases
  • webfetch-validation.test.ts: 24 tests — URL validation, cache TTL, error messages, edge cases
  • Existing 76 tests unchanged and passing
  • Marker guard: ✅ (altimate_change markers on webfetch.ts)
  • Type check: ✅ (no new errors)

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • New and existing unit tests pass locally with my changes
  • I have added tests that prove my fix is effective

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved edit failure messages to include nearest-match context when a target string isn't found.
    • Enhanced web fetch behavior with stricter URL validation, status-specific actionable errors, and short-term caching of certain permanent failures.
    • Added a robust fallback for SQL read/write classification when the native classifier is unavailable, preventing throws on malformed or large inputs.
  • Tests

    • Added comprehensive tests covering fallback classification, edit-error context messaging, webfetch validation, and native-export availability checks.

**#469 — `sql_execute` crash: `getStatementTypes is not a function`**
- Safe-import `@altimateai/altimate-core` with `typeof` guard
- Regex fallback classifier when napi binary unavailable
- Null/undefined input guards on `classify()` and `classifyAndCheck()`
- 42 adversarial tests (fallback parity, ReDoS, bypass attempts)

**#470 — `edit` tool: "Could not find oldString" context drift**
- `buildNotFoundMessage()` finds closest-matching line via Levenshtein
- Error now shows line number + 5-line snippet of actual file content
- Tells model to re-read the file instead of retrying blindly
- 14 adversarial tests (similarity scoring, truncation, edge cases)

**#471 — `webfetch`: 934 daily failures from invalid/broken URLs**
- `URL` constructor validation before fetch (catches malformed URLs)
- Session-level 404/410/451 failure cache with 5-min TTL
- Actionable error messages per HTTP status (404: "Do NOT retry",
  429: includes `Retry-After`, 500: "transient — retry once")
- 24 tests (validation, cache TTL, error messages, edge cases)

**#473 — Bump `yaml` 2.8.2 → 2.8.3**
- Stack overflow fix during node composition (security)

Closes #469, Closes #470, Closes #471, Closes #473

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link

@claude claude bot left a comment

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review.

Tip: disable this comment in your organization's Code Review settings.

@coderabbitai
Copy link

coderabbitai bot commented Mar 26, 2026

📝 Walkthrough

Walkthrough

SQL classification now guards native N-API usage and falls back to a regex classifier when unavailable; edit tooling reports richer "not found" diagnostics via a new helper; webfetch adds URL validation, status-aware errors, and a session-scoped failure cache; multiple tests and a pre-release NAPI export check were added.

Changes

Cohort / File(s) Summary
SQL Classification
packages/opencode/src/altimate/tools/sql-classify.ts
Guarded loading of @altimateai/altimate-core; added classifyFallback(sql) regex classifier; classify() and classifyAndCheck() now use AST when available and fall back safely to regex-based classification (including blocked detection). Removed unused WRITE_CATEGORIES.
SQL Classification Tests
packages/opencode/test/altimate/tools/sql-classify-fallback.test.ts
New adversarial tests exercising fallback behavior (null/undefined, empty, very long, malformed, control chars), read/write detection, hard-deny patterns, comments/strings bypass cases, ReDoS/performance checks, and AST vs regex consistency checks when NAPI is present.
Edit Tool
packages/opencode/src/tool/edit.ts
Added exported buildNotFoundMessage(content, oldString) that finds nearest-match context (substring + Levenshtein heuristics) and updated replace() to throw detailed context-aware errors when oldString isn't found.
Edit Tool Tests
packages/opencode/test/tool/edit-error-context.test.ts
New tests validating nearest-match detection, snippet line markers, bounded snippet size, handling empty/whitespace search terms, exact-match behavior, and integration coverage for replace() error cases.
Web Fetch
packages/opencode/src/tool/webfetch.ts
Added new URL() validation, status-specific actionable error messages (404/410/403/429/451), session-scoped failure cache (5-minute TTL) for permanent-ish failures (404/410/451), and preserved retry behavior for retryable statuses.
Web Fetch Tests
packages/opencode/test/tool/webfetch-validation.test.ts
New tests for URL scheme validation, new URL() parsing, status-message content (including 429 retry-after), failure-cache semantics (which statuses are cached, TTL, expiry), and URL edge cases (unicode, percent-encoding, long URLs, auth/ports/fragments).
Pre-release NAPI Check
packages/opencode/script/pre-release-check.ts
Added "Check 2b" to dynamically verify required NAPI export names exist and are functions; failure reports a "stale platform binary" remediation message; adjusted progress numbering.
NAPI Export CI Test
packages/opencode/test/altimate/napi-exports.test.ts
New CI test asserting presence and shape of critical @altimateai/altimate-core exports, verifying getStatementTypes("SELECT 1") shape and minimal function-count sanity.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant WebFetch as WebFetchModule
  participant Cache as FailureCache
  participant Remote as RemoteServer

  Client->>WebFetch: request(url)
  WebFetch->>Cache: check(url)
  alt cached permanent failure
    Cache-->>WebFetch: cached error
    WebFetch-->>Client: throw cached error
  else not cached
    WebFetch->>Remote: fetch(url)
    Remote-->>WebFetch: response(status, headers, body)
    alt response.ok === false
      WebFetch->>Cache: record(url) if status in (404,410,451)
      WebFetch-->>Client: throw status-aware error (may include Retry-After)
    else response.ok
      WebFetch-->>Client: return body
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • suryaiyer95

Poem

🐰 I hop through code paths, nimble and spry,

When natives are missing, regex steps by.
Errors now whisper where matches miss sight,
Caches remember when servers don’t bite.
A rabbit's small cheer for safer deploys tonight.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: fixing reliability issues in three tools (sql_execute, edit, webfetch) with specific failure types (crash, context drift, failures). It is concise and directly related to the changeset.
Description check ✅ Passed The PR description fully matches the template structure with clear Summary, Test Plan, and Checklist sections. It comprehensively explains what changed, why, how it was tested, and includes all required checkboxes.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/469-470-471-473-tool-reliability

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link

@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: 6

🧹 Nitpick comments (3)
packages/opencode/test/tool/webfetch-validation.test.ts (2)

11-12: Risk of test drift from duplicated logic.

Replicating private functions means tests may pass while actual implementation diverges. Consider either:

  1. Exporting the helpers (even if marked @internal)
  2. Adding integration tests that exercise the actual tool with mocked HTTP responses

The current approach is acceptable for initial coverage, but keep this drift risk in mind during future changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/test/tool/webfetch-validation.test.ts` around lines 11 -
12, The test duplicates private helper logic which risks drift; fix by either
exporting the internal helper functions used by the webfetch validation (make
the helpers exportable—mark with `@internal` if desired) so the test in
webfetch-validation.test.ts can import and assert real behavior, or
replace/augment the unit-style duplication with an integration test that invokes
the actual webfetch tool (the webfetch entry/function used by the tool) against
mocked HTTP responses to validate behavior end-to-end; update imports in the
test to use the exported helpers or call the real webfetch function accordingly.

146-149: Shared cache across tests risks flaky behavior.

The cache Map is shared across all tests in this describe block without cleanup. While unique URLs prevent immediate collisions, tests that modify the cache (lines 167-207) accumulate state. Consider clearing the cache before each test for isolation.

Suggested fix
 describe("URL failure cache", () => {
   // Replicate cache logic for testing
   const cache = new Map<string, { status: number; timestamp: number }>()
   const TTL = 5 * 60 * 1000
+
+  // Clear cache before each test for isolation
+  beforeEach(() => {
+    cache.clear()
+  })

Also add the import:

-import { describe, test, expect } from "bun:test"
+import { describe, test, expect, beforeEach } from "bun:test"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/test/tool/webfetch-validation.test.ts` around lines 146 -
149, The shared Map named cache (and its TTL constant) in the describe block
"URL failure cache" causes cross-test state leakage; add a test isolation step
to clear/reset the cache before each test (e.g., a beforeEach that calls
cache.clear()) so tests modifying cache entries (the tests around lines that
update the cache) start with a clean map; locate the Map by the identifier cache
and the describe block name to implement the cleanup.
packages/opencode/src/tool/webfetch.ts (1)

21-22: Consider adding a cache size limit to prevent unbounded memory growth.

The cache only cleans up entries lazily (on access after TTL expires). In a long-running process fetching many unique failing URLs, the map could grow indefinitely. Consider adding a max size with LRU eviction, or periodic cleanup.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/src/tool/webfetch.ts` around lines 21 - 22, The failure
cache (failedUrls) can grow unbounded because entries are only expired lazily
using FAILURE_CACHE_TTL; update the logic to enforce a max size and evict
least-recently-used entries (or run periodic cleanup): introduce a
MAX_FAILURE_CACHE_SIZE constant and either replace Map failedUrls with an LRU
structure or maintain access-order by touching entries on read and evicting
oldest when size > MAX_FAILURE_CACHE_SIZE, and/or add a periodic timer that
purges entries older than FAILURE_CACHE_TTL; ensure you update all usages of
failedUrls (look for references to failedUrls and FAILURE_CACHE_TTL) to use the
new eviction/cleanup behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/altimate/tools/sql-classify.ts`:
- Around line 28-29: The HARD_DENY_PATTERN check is only applied to the start of
the normalized SQL and normalization currently only strips block comments, so
inputs with leading line comments or multiple statements (e.g. "-- note\nDROP
DATABASE..." or "SELECT 1; DROP DATABASE ...") bypass the deny. Update the
normalization in sql-classify.ts to remove leading/trailing whitespace, strip
both block (/* */) and line (--) comments, then split the input into individual
statements by semicolon (or conservatively detect multiple statements) and apply
HARD_DENY_PATTERN to every trimmed statement; alternatively, reject any input
containing multiple statements outright. Also ensure the code path used by
sql-execute.ts that relies on the blocked flag uses this tightened
classification.
- Around line 25-27: The current fallback uses WRITE_PATTERN and defaults
anything not matching that small write-prefix list to "read", which
under-prompts ambiguous statements; change the fallback to use a positive read
allowlist instead and treat everything else as "write". Specifically, replace or
augment the WRITE_PATTERN usage with a READ_PATTERN that only matches clearly
read-only statements (e.g., SELECT, WITH ... SELECT, EXPLAIN,
DESCRIBE/SHOW/PRAGMA/SET variants you deem safe) and update the fallback logic
(the code that currently returns "read" when WRITE_PATTERN fails) to return
"write" by default unless READ_PATTERN matches; keep the WRITE_PATTERN checks
for explicit write-prefixes
(INSERT/UPDATE/DELETE/MERGE/CREATE/ALTER/DROP/TRUNCATE/GRANT/REVOKE/CALL/EXEC)
to still classify obvious writes.

In `@packages/opencode/src/tool/edit.ts`:
- Around line 639-642: The code currently uses the literal first line of
oldString which treats inputs like "\n\nactual text" as empty; update the logic
that sets firstLine in edit.ts to scan oldString's lines and pick the first
non-empty trimmed line (e.g., split by "\n", trim each, find first with length >
0) and only fall back to the empty/whitespace warning if no non-empty line
exists; adjust the subsequent check that returns base + " The oldString appears
to be empty or whitespace-only." to use this new first non-empty trimmed line so
nearest-match hints work for strings with leading blank lines.

In `@packages/opencode/src/tool/webfetch.ts`:
- Around line 50-54: The 429 handling currently appends "s" to the Retry-After
header unconditionally; change the logic in the case 429 block (variables
retryAfter and wait in packages/opencode/src/tool/webfetch.ts) to detect whether
headers?.get("retry-after") is a numeric-second value or a HTTP-date string: if
it's a positive integer use " (retry after Xs)", if it's a non-numeric/date
string include it verbatim like " (retry after <value>)" or omit the unit, and
ensure the message remains valid by not appending "s" to date values; keep the
rest of the message about the hostname and suggested actions intact.
- Around line 18-22: The comment for the failedUrls cache incorrectly states it
is session-level, but it is actually process-level because it is a module-scoped
Map reused across all sessions. To fix this, update the comment above the
failedUrls declaration to accurately describe it as a process-level URL failure
cache that persists for the process lifetime and is cleared only when the
process restarts. Remove or revise any language implying it is scoped per user
session.

In `@packages/opencode/test/altimate/tools/sql-classify-fallback.test.ts`:
- Around line 68-83: Test reimplements WRITE_PATTERN/HARD_DENY_PATTERN and
classifyFallback locally, so it doesn't exercise the module's real fallback
branch; instead import the actual fallback patterns or force a real import
failure so the production fallback is exercised. Update the test to either (a)
import the exported fallback regexes/utility from sql-classify.ts (referencing
the module's exported symbols like WRITE_PATTERN, HARD_DENY_PATTERN or a helper
such as getFallbackPatterns/classifyFallbackFromModule) and use those in
assertions, or (b) simulate a real N-API load failure by mocking the
require/import of '@altimateai/altimate-core' to throw (using your test
framework's module-mocking API) before importing sql-classify.ts so the module's
fallback path runs and the tests assert against the module's actual fallback
implementation.

---

Nitpick comments:
In `@packages/opencode/src/tool/webfetch.ts`:
- Around line 21-22: The failure cache (failedUrls) can grow unbounded because
entries are only expired lazily using FAILURE_CACHE_TTL; update the logic to
enforce a max size and evict least-recently-used entries (or run periodic
cleanup): introduce a MAX_FAILURE_CACHE_SIZE constant and either replace Map
failedUrls with an LRU structure or maintain access-order by touching entries on
read and evicting oldest when size > MAX_FAILURE_CACHE_SIZE, and/or add a
periodic timer that purges entries older than FAILURE_CACHE_TTL; ensure you
update all usages of failedUrls (look for references to failedUrls and
FAILURE_CACHE_TTL) to use the new eviction/cleanup behavior.

In `@packages/opencode/test/tool/webfetch-validation.test.ts`:
- Around line 11-12: The test duplicates private helper logic which risks drift;
fix by either exporting the internal helper functions used by the webfetch
validation (make the helpers exportable—mark with `@internal` if desired) so the
test in webfetch-validation.test.ts can import and assert real behavior, or
replace/augment the unit-style duplication with an integration test that invokes
the actual webfetch tool (the webfetch entry/function used by the tool) against
mocked HTTP responses to validate behavior end-to-end; update imports in the
test to use the exported helpers or call the real webfetch function accordingly.
- Around line 146-149: The shared Map named cache (and its TTL constant) in the
describe block "URL failure cache" causes cross-test state leakage; add a test
isolation step to clear/reset the cache before each test (e.g., a beforeEach
that calls cache.clear()) so tests modifying cache entries (the tests around
lines that update the cache) start with a clean map; locate the Map by the
identifier cache and the describe block name to implement the cleanup.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 300a36f9-8f7a-4c67-a29e-8aa9cfc70efd

📥 Commits

Reviewing files that changed from the base of the PR and between 959d580 and 627d80e.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • packages/opencode/src/altimate/tools/sql-classify.ts
  • packages/opencode/src/tool/edit.ts
  • packages/opencode/src/tool/webfetch.ts
  • packages/opencode/test/altimate/tools/sql-classify-fallback.test.ts
  • packages/opencode/test/tool/edit-error-context.test.ts
  • packages/opencode/test/tool/webfetch-validation.test.ts

Comment on lines +25 to +27
// Regex fallback: patterns that indicate write operations (case-insensitive, anchored to statement start)
const WRITE_PATTERN =
/^\s*(INSERT|UPDATE|DELETE|MERGE|CREATE|ALTER|DROP|TRUNCATE|GRANT|REVOKE|CALL|EXEC)\b/i
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fallback now under-prompts on ambiguous SQL.

This branch returns "read" for anything outside the small write-prefix list, so fallback mode will silently skip the sql_execute_write prompt for statements the comment above explicitly calls ambiguous, like SHOW, SET, and USE, plus forms like WITH ... DELETE. Since this is the degraded path, false positives are safer than false negatives here — please switch to a positive read allowlist and default everything else to "write".

Minimal safe direction
-const WRITE_PATTERN =
-  /^\s*(INSERT|UPDATE|DELETE|MERGE|CREATE|ALTER|DROP|TRUNCATE|GRANT|REVOKE|CALL|EXEC)\b/i
+const SAFE_READ_PATTERN = /^\s*SELECT\b/i
...
-  const queryType = WRITE_PATTERN.test(trimmed) ? "write" : "read"
+  const queryType = SAFE_READ_PATTERN.test(trimmed) ? "read" : "write"

Also applies to: 38-38

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/src/altimate/tools/sql-classify.ts` around lines 25 - 27,
The current fallback uses WRITE_PATTERN and defaults anything not matching that
small write-prefix list to "read", which under-prompts ambiguous statements;
change the fallback to use a positive read allowlist instead and treat
everything else as "write". Specifically, replace or augment the WRITE_PATTERN
usage with a READ_PATTERN that only matches clearly read-only statements (e.g.,
SELECT, WITH ... SELECT, EXPLAIN, DESCRIBE/SHOW/PRAGMA/SET variants you deem
safe) and update the fallback logic (the code that currently returns "read" when
WRITE_PATTERN fails) to return "write" by default unless READ_PATTERN matches;
keep the WRITE_PATTERN checks for explicit write-prefixes
(INSERT/UPDATE/DELETE/MERGE/CREATE/ALTER/DROP/TRUNCATE/GRANT/REVOKE/CALL/EXEC)
to still classify obvious writes.

Comment on lines +28 to +29
const HARD_DENY_PATTERN =
/^\s*(DROP\s+(DATABASE|SCHEMA)\b|TRUNCATE(\s+TABLE)?\s)/i
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fallback hard-deny is still bypassable.

HARD_DENY_PATTERN is only checked against the start of the normalized string, and the normalization step only removes block comments. In degraded mode, inputs like -- note\nDROP DATABASE prod or SELECT 1; DROP DATABASE prod still return blocked: false. packages/opencode/src/altimate/tools/sql-execute.ts:16-30 relies on this flag to stop execution, so the fallback needs to strip leading line comments and inspect every apparent statement (or conservatively reject multi-statement input) before returning.

Also applies to: 36-37

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/src/altimate/tools/sql-classify.ts` around lines 28 - 29,
The HARD_DENY_PATTERN check is only applied to the start of the normalized SQL
and normalization currently only strips block comments, so inputs with leading
line comments or multiple statements (e.g. "-- note\nDROP DATABASE..." or
"SELECT 1; DROP DATABASE ...") bypass the deny. Update the normalization in
sql-classify.ts to remove leading/trailing whitespace, strip both block (/* */)
and line (--) comments, then split the input into individual statements by
semicolon (or conservatively detect multiple statements) and apply
HARD_DENY_PATTERN to every trimmed statement; alternatively, reject any input
containing multiple statements outright. Also ensure the code path used by
sql-execute.ts that relies on the blocked flag uses this tightened
classification.

Comment on lines +639 to +642
// Find the first line of oldString and search for it in the file
const firstLine = oldString.split("\n")[0].trim()
if (!firstLine) return base + " The oldString appears to be empty or whitespace-only."

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use the first non-empty line from oldString, not the literal first line.

This currently misclassifies inputs like "\n\nactual text" as whitespace-only and skips nearest-match hints. Please scan for the first non-empty trimmed line before fallback.

Proposed fix
-  const firstLine = oldString.split("\n")[0].trim()
+  const firstLine = oldString
+    .split("\n")
+    .map((line) => line.trim())
+    .find((line) => line.length > 0) ?? ""
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/src/tool/edit.ts` around lines 639 - 642, The code
currently uses the literal first line of oldString which treats inputs like
"\n\nactual text" as empty; update the logic that sets firstLine in edit.ts to
scan oldString's lines and pick the first non-empty trimmed line (e.g., split by
"\n", trim each, find first with length > 0) and only fall back to the
empty/whitespace warning if no non-empty line exists; adjust the subsequent
check that returns base + " The oldString appears to be empty or
whitespace-only." to use this new first non-empty trimmed line so nearest-match
hints work for strings with leading blank lines.

Comment on lines +18 to +22
// altimate_change start — session-level URL failure cache (#471)
// Prevents repeated fetches to URLs that already returned 404/410 in this session.
// Keyed by URL string. Cleared when the process restarts (new session).
const failedUrls = new Map<string, { status: number; timestamp: number }>()
const FAILURE_CACHE_TTL = 5 * 60 * 1000 // 5 minutes
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Misleading comment: cache is process-level, not session-level.

Based on the context snippet from tool.ts, Tool.define() is called once at module load time and the same tool instance is reused across all sessions. The failedUrls Map is module-scoped and persists across all sessions within the same process, not just a single session.

This could cause unintended behavior where a 404 cached from one user's session affects another user's session in the same process.

-// altimate_change start — session-level URL failure cache (`#471`)
-// Prevents repeated fetches to URLs that already returned 404/410 in this session.
-// Keyed by URL string. Cleared when the process restarts (new session).
+// altimate_change start — process-level URL failure cache (`#471`)
+// Prevents repeated fetches to URLs that already returned 404/410 in this process.
+// Keyed by URL string. Shared across all sessions; cleared when the process restarts.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/src/tool/webfetch.ts` around lines 18 - 22, The comment for
the failedUrls cache incorrectly states it is session-level, but it is actually
process-level because it is a module-scoped Map reused across all sessions. To
fix this, update the comment above the failedUrls declaration to accurately
describe it as a process-level URL failure cache that persists for the process
lifetime and is cleared only when the process restarts. Remove or revise any
language implying it is scoped per user session.

Comment on lines +50 to +54
case 429: {
const retryAfter = headers?.get("retry-after")
const wait = retryAfter ? ` (retry after ${retryAfter}s)` : ""
return `HTTP 429: Rate limited by ${new URL(url).hostname}${wait}. Wait before fetching from this domain again, or use a different source.`
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

retry-after header can be a date string, not just seconds.

Per HTTP spec, the Retry-After header can be either a number of seconds or an HTTP-date (e.g., "Fri, 31 Dec 2026 23:59:59 GMT"). Appending "s" unconditionally produces malformed messages for date values.

Suggested fix
     case 429: {
       const retryAfter = headers?.get("retry-after")
-      const wait = retryAfter ? ` (retry after ${retryAfter}s)` : ""
+      let wait = ""
+      if (retryAfter) {
+        // retry-after can be seconds or HTTP-date; only append "s" for numeric values
+        wait = /^\d+$/.test(retryAfter)
+          ? ` (retry after ${retryAfter}s)`
+          : ` (retry after ${retryAfter})`
+      }
       return `HTTP 429: Rate limited by ${new URL(url).hostname}${wait}. Wait before fetching from this domain again, or use a different source.`
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case 429: {
const retryAfter = headers?.get("retry-after")
const wait = retryAfter ? ` (retry after ${retryAfter}s)` : ""
return `HTTP 429: Rate limited by ${new URL(url).hostname}${wait}. Wait before fetching from this domain again, or use a different source.`
}
case 429: {
const retryAfter = headers?.get("retry-after")
let wait = ""
if (retryAfter) {
// retry-after can be seconds or HTTP-date; only append "s" for numeric values
wait = /^\d+$/.test(retryAfter)
? ` (retry after ${retryAfter}s)`
: ` (retry after ${retryAfter})`
}
return `HTTP 429: Rate limited by ${new URL(url).hostname}${wait}. Wait before fetching from this domain again, or use a different source.`
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/src/tool/webfetch.ts` around lines 50 - 54, The 429
handling currently appends "s" to the Retry-After header unconditionally; change
the logic in the case 429 block (variables retryAfter and wait in
packages/opencode/src/tool/webfetch.ts) to detect whether
headers?.get("retry-after") is a numeric-second value or a HTTP-date string: if
it's a positive integer use " (retry after Xs)", if it's a non-numeric/date
string include it verbatim like " (retry after <value>)" or omit the unit, and
ensure the message remains valid by not appending "s" to date values; keep the
rest of the message about the hostname and suggested actions intact.

Comment on lines +68 to +83
describe("regex fallback — simulated napi failure", () => {
// We test the fallback logic by directly calling the internal regex patterns.
// Since we can't mock require() in the already-loaded module, we replicate
// the fallback logic from the source to verify regex correctness.

const WRITE_PATTERN =
/^\s*(INSERT|UPDATE|DELETE|MERGE|CREATE|ALTER|DROP|TRUNCATE|GRANT|REVOKE|CALL|EXEC)\b/i
const HARD_DENY_PATTERN =
/^\s*(DROP\s+(DATABASE|SCHEMA)\b|TRUNCATE(\s+TABLE)?\s)/i

function classifyFallback(sql: string): { queryType: "read" | "write"; blocked: boolean } {
const trimmed = sql.replace(/\/\*[\s\S]*?\*\//g, "").trim()
const blocked = HARD_DENY_PATTERN.test(trimmed)
const queryType = WRITE_PATTERN.test(trimmed) ? "write" : "read"
return { queryType, blocked }
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

These tests can stay green without exercising the implementation paths they claim to cover.

The “simulated napi failure” block reimplements the regex locally instead of forcing sql-classify.ts through its real fallback branch, and the parity suite never proves the AST path is actually active. On a host where @altimateai/altimate-core fails to load, both suites can still pass while the shipped fallback logic drifts. Please share the real helper/patterns with the test, or load the module under an actual import failure so these assertions are coupled to production code.

Also applies to: 244-258

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/test/altimate/tools/sql-classify-fallback.test.ts` around
lines 68 - 83, Test reimplements WRITE_PATTERN/HARD_DENY_PATTERN and
classifyFallback locally, so it doesn't exercise the module's real fallback
branch; instead import the actual fallback patterns or force a real import
failure so the production fallback is exercised. Update the test to either (a)
import the exported fallback regexes/utility from sql-classify.ts (referencing
the module's exported symbols like WRITE_PATTERN, HARD_DENY_PATTERN or a helper
such as getFallbackPatterns/classifyFallbackFromModule) and use those in
assertions, or (b) simulate a real N-API load failure by mocking the
require/import of '@altimateai/altimate-core' to throw (using your test
framework's module-mocking API) before importing sql-classify.ts so the module's
fallback path runs and the tests assert against the module's actual fallback
implementation.

Prevents stale `@altimateai/altimate-core` platform binaries from
reaching production. Two new defense layers:

1. **CI test** (`napi-exports.test.ts`): verifies all 40+ expected
   function exports exist in the installed binary. Fails CI if a
   version bump doesn't update the lock file.

2. **Pre-release check** (step 2b): validates 17 critical napi exports
   before `bun run pre-release` passes. Blocks release tagging when
   the binary is stale.

Together with the runtime validation in altimate-core-internal PR #113,
this creates 3 layers of defense:
  - Build-time: validate-exports.js patches index.js (core-internal)
  - CI: napi-exports.test.ts fails on missing exports (altimate-code)
  - Pre-release: pre-release-check.ts blocks stale binaries (altimate-code)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/script/pre-release-check.ts`:
- Around line 70-75: The pre-release export gate only lists a subset of required
N-API exports; update the CRITICAL_EXPORTS array to include the full export
contract used elsewhere so missing symbols can't slip through; locate the
CRITICAL_EXPORTS constant and add every exported function name that the N-API
consumer/validator expects (i.e., mirror the complete list of exports validated
in the N-API contract) so this file's check and the N-API validation are
aligned.

In `@packages/opencode/test/altimate/napi-exports.test.ts`:
- Around line 104-113: The test for getStatementTypes only checks top-level keys
but must also verify the nested statements shape used at runtime; update the
"getStatementTypes returns expected shape" test (and any helper calls to
core.getStatementTypes) to assert that result.statements is an array, that its
length matches result.statement_count, and that each item in result.statements
has a statement_type property (and optionally type/category fields used
downstream), and keep the existing checks for statement_count and categories
(e.g., ensure at least one statement has statement_type === "query").
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f1cce881-864d-45dd-a6b0-54a1e5f6a02a

📥 Commits

Reviewing files that changed from the base of the PR and between 627d80e and a290b43.

📒 Files selected for processing (2)
  • packages/opencode/script/pre-release-check.ts
  • packages/opencode/test/altimate/napi-exports.test.ts

Comment on lines +70 to +75
const CRITICAL_EXPORTS = [
"getStatementTypes", "formatSql", "lint", "validate", "transpile",
"extractMetadata", "columnLineage", "trackLineage", "diffSchemas",
"importDdl", "exportDdl", "optimizeContext", "pruneSchema",
"compareQueries", "classifyPii", "checkQueryPii", "parseDbtProject",
]
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Pre-release export gate is too narrow and can miss broken releases.

This list covers only a subset of the N-API contract validated elsewhere, so a binary with missing non-listed exports can still pass this pre-release check.

🔧 Proposed alignment with full export contract
 const CRITICAL_EXPORTS = [
-  "getStatementTypes", "formatSql", "lint", "validate", "transpile",
-  "extractMetadata", "columnLineage", "trackLineage", "diffSchemas",
-  "importDdl", "exportDdl", "optimizeContext", "pruneSchema",
-  "compareQueries", "classifyPii", "checkQueryPii", "parseDbtProject",
+  "transpile",
+  "formatSql",
+  "extractMetadata",
+  "extractOutputColumns",
+  "getStatementTypes",
+  "compareQueries",
+  "optimizeContext",
+  "optimizeForQuery",
+  "pruneSchema",
+  "diffSchemas",
+  "importDdl",
+  "exportDdl",
+  "schemaFingerprint",
+  "introspectionSql",
+  "lint",
+  "scanSql",
+  "isSafe",
+  "classifyPii",
+  "checkQueryPii",
+  "resolveTerm",
+  "analyzeTags",
+  "columnLineage",
+  "diffLineage",
+  "trackLineage",
+  "complete",
+  "rewrite",
+  "generateTests",
+  "analyzeMigration",
+  "parseDbtProject",
+  "correct",
+  "evaluate",
+  "explain",
+  "fix",
+  "validate",
+  "checkEquivalence",
+  "checkPolicy",
+  "checkSemantics",
+  "initSdk",
+  "resetSdk",
+  "flushSdk",
 ]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const CRITICAL_EXPORTS = [
"getStatementTypes", "formatSql", "lint", "validate", "transpile",
"extractMetadata", "columnLineage", "trackLineage", "diffSchemas",
"importDdl", "exportDdl", "optimizeContext", "pruneSchema",
"compareQueries", "classifyPii", "checkQueryPii", "parseDbtProject",
]
const CRITICAL_EXPORTS = [
"transpile",
"formatSql",
"extractMetadata",
"extractOutputColumns",
"getStatementTypes",
"compareQueries",
"optimizeContext",
"optimizeForQuery",
"pruneSchema",
"diffSchemas",
"importDdl",
"exportDdl",
"schemaFingerprint",
"introspectionSql",
"lint",
"scanSql",
"isSafe",
"classifyPii",
"checkQueryPii",
"resolveTerm",
"analyzeTags",
"columnLineage",
"diffLineage",
"trackLineage",
"complete",
"rewrite",
"generateTests",
"analyzeMigration",
"parseDbtProject",
"correct",
"evaluate",
"explain",
"fix",
"validate",
"checkEquivalence",
"checkPolicy",
"checkSemantics",
"initSdk",
"resetSdk",
"flushSdk",
]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/script/pre-release-check.ts` around lines 70 - 75, The
pre-release export gate only lists a subset of required N-API exports; update
the CRITICAL_EXPORTS array to include the full export contract used elsewhere so
missing symbols can't slip through; locate the CRITICAL_EXPORTS constant and add
every exported function name that the N-API consumer/validator expects (i.e.,
mirror the complete list of exports validated in the N-API contract) so this
file's check and the N-API validation are aligned.

Comment on lines +104 to +113
test("getStatementTypes returns expected shape", () => {
if (!core || typeof core.getStatementTypes !== "function") return
const result = (core.getStatementTypes as (sql: string) => any)("SELECT 1")
expect(result).toHaveProperty("statements")
expect(result).toHaveProperty("statement_count")
expect(result).toHaveProperty("types")
expect(result).toHaveProperty("categories")
expect(result.statement_count).toBe(1)
expect(result.categories).toContain("query")
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Strengthen the contract check for getStatementTypes nested statement shape.

Current assertions only validate top-level keys. Runtime code also depends on statements[].statement_type, so this test can miss a breaking N-API shape change.

🔧 Proposed test hardening
 test("getStatementTypes returns expected shape", () => {
   if (!core || typeof core.getStatementTypes !== "function") return
   const result = (core.getStatementTypes as (sql: string) => any)("SELECT 1")
   expect(result).toHaveProperty("statements")
   expect(result).toHaveProperty("statement_count")
   expect(result).toHaveProperty("types")
   expect(result).toHaveProperty("categories")
   expect(result.statement_count).toBe(1)
   expect(result.categories).toContain("query")
+  expect(Array.isArray(result.statements)).toBe(true)
+  expect(result.statements.length).toBeGreaterThan(0)
+  expect(result.statements[0]).toHaveProperty("statement_type")
+  expect(typeof result.statements[0].statement_type).toBe("string")
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test("getStatementTypes returns expected shape", () => {
if (!core || typeof core.getStatementTypes !== "function") return
const result = (core.getStatementTypes as (sql: string) => any)("SELECT 1")
expect(result).toHaveProperty("statements")
expect(result).toHaveProperty("statement_count")
expect(result).toHaveProperty("types")
expect(result).toHaveProperty("categories")
expect(result.statement_count).toBe(1)
expect(result.categories).toContain("query")
})
test("getStatementTypes returns expected shape", () => {
if (!core || typeof core.getStatementTypes !== "function") return
const result = (core.getStatementTypes as (sql: string) => any)("SELECT 1")
expect(result).toHaveProperty("statements")
expect(result).toHaveProperty("statement_count")
expect(result).toHaveProperty("types")
expect(result).toHaveProperty("categories")
expect(result.statement_count).toBe(1)
expect(result.categories).toContain("query")
expect(Array.isArray(result.statements)).toBe(true)
expect(result.statements.length).toBeGreaterThan(0)
expect(result.statements[0]).toHaveProperty("statement_type")
expect(typeof result.statements[0].statement_type).toBe("string")
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/test/altimate/napi-exports.test.ts` around lines 104 - 113,
The test for getStatementTypes only checks top-level keys but must also verify
the nested statements shape used at runtime; update the "getStatementTypes
returns expected shape" test (and any helper calls to core.getStatementTypes) to
assert that result.statements is an array, that its length matches
result.statement_count, and that each item in result.statements has a
statement_type property (and optionally type/category fields used downstream),
and keep the existing checks for statement_count and categories (e.g., ensure at
least one statement has statement_type === "query").

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

Projects

None yet

1 participant