feat(bolt): add --offline flag that fails fast without network - #393
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
|
Warning Review limit reached
Next review available in: 52 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📥 CommitsReviewing files that changed from the base of the PR and between 7c7f7baca4f5b8a4e955fea66d5c520ceef7bd93 and df00357. 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds offline mode to the CLI. Offline mode allows local destinations, rejects external fetches, sets ChangesOffline mode
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant CLI as CLI middleware
participant Offline as Offline.enable()
participant Fetch as globalThis.fetch
participant LocalServer as Bun local server
CLI->>Offline: enable offline mode
Offline->>Fetch: install fetch guard
Fetch->>Offline: receive request URL
Offline->>LocalServer: forward local request
Offline-->>Fetch: reject external request
``
</details>
<!-- walkthrough_end -->
<!-- pre_merge_checks_walkthrough_start -->
<details>
<summary>🚥 Pre-merge checks | ✅ 4 | ❌ 1</summary>
### ❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
| :---------------: | :--------- | :------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------- |
| Description check | ⚠️ Warning | The description explains the change and verification, but it omits the issue, type, screenshots, and checklist sections required by the template. | Complete the required template sections, including the issue reference, change type, screenshots note, and checklist confirmations. |
<details>
<summary>✅ Passed checks (4 passed)</summary>
| Check name | Status | Explanation |
| :------------------------: | :------- | :--------------------------------------------------------------------------------------------------------------- |
| Title check | ✅ Passed | The title clearly and concisely describes the addition of the --offline flag and its fail-fast network behavior. |
| Docstring Coverage | ✅ Passed | No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. |
| 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. |
</details>
</details>
<!-- pre_merge_checks_walkthrough_end -->
<!-- finishing_touch_checkbox_start -->
<details>
<summary>✨ Finishing Touches 💡 2</summary>
<!-- finishing_touch_suggestion:resolve_merge_conflict -->
<details open>
<summary>⚔️ Resolve merge conflicts 💡</summary>
- [ ] <!-- {"checkboxId": "c3a5b2e1-4d7f-4a8c-b9d6-e1f2c3d4a5b6"} --> Resolve merge conflict in branch `offline-flag`
</details>
<!-- finishing_touch_suggestion:fix_ci -->
<details open>
<summary>🛠️ Fix failing CI checks 💡</summary>
- [ ] <!-- {"checkboxId": "6d21cfe8-ec3f-40e2-9222-b8318b64d3b0", "radioGroupId": "fix-ci-output-choice-group-unknown_comment_id"} --> Create stacked PR
- [ ] <!-- {"checkboxId": "9f0d24fb-b419-4f01-baf0-8b26b6424f34", "radioGroupId": "fix-ci-output-choice-group-unknown_comment_id"} --> Commit on current branch
</details>
<details>
<summary>📝 Generate docstrings</summary>
- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> Create stacked PR
- [ ] <!-- {"checkboxId": "3e1879ae-f29b-4d0d-8e06-d12b7ba33d98"} --> Commit on current branch
</details>
<details>
<summary>🧪 Generate unit tests (beta)</summary>
- [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Create PR with unit tests
- [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Commit unit tests in branch `offline-flag`
</details>
</details>
<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->
---
<sub>Comment `@coderabbitai help` to get the list of available commands.</sub>
<!-- tips_end -->
|
|
Deployment failed for project bolt-cli-app with the following error: Learn More: https://vercel.com/adevloper152s-projects?upgradeToPro=build-rate-limit |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
packages/opencode/src/cli/offline.ts (1)
1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMove the self-reexport to the end of the module.
Keep the declarations first. Put
export * as Offline from "./offline"afterenable.As per coding guidelines, use a bottom-of-file self-reexport such as
export * as Foo from "./foo".Proposed fix
-export * as Offline from "./offline" - // Offline mode (--offline): instead of letting network calls hang until their @@ export function enable() { @@ } + +export * as Offline from "./offline"🤖 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 `@packages/opencode/src/cli/offline.ts` at line 1, Move the self-reexport `Offline` to the bottom of the module, after the `enable` declaration, while keeping all existing declarations and behavior unchanged.Source: Coding guidelines
🤖 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 `@packages/opencode/src/cli/offline.ts`:
- Around line 10-12: Update local to classify every IPv4 address in the
127.0.0.0/8 loopback range as local, not only the hostname currently covered by
LOCAL; add a test verifying that 127.0.0.2 is accepted while preserving existing
classifications.
- Around line 20-27: Update the fetch guard in enable and its local URL
validation to explicitly allow file:, data:, and blob: protocols, while
continuing to reject s3: and other non-local requests. Add coverage verifying
offline fetch permits each of the three resource schemes and keeps s3: blocked.
In `@packages/opencode/test/cli/offline.test.ts`:
- Around line 24-27: Await the rejected-fetch assertion in the “blocks remote
requests immediately” test so the test waits for and validates the asynchronous
rejection. Keep the existing offline-mode error expectation and timing assertion
unchanged.
---
Nitpick comments:
In `@packages/opencode/src/cli/offline.ts`:
- Line 1: Move the self-reexport `Offline` to the bottom of the module, after
the `enable` declaration, while keeping all existing declarations and behavior
unchanged.
🪄 Autofix
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 Plus
Run ID: ddf0d2a7-cf8c-47d0-81cd-b180624d63a2
📥 Commits
Reviewing files that changed from the base of the PR and between e119f41 and 7c7f7baca4f5b8a4e955fea66d5c520ceef7bd93.
📒 Files selected for processing (3)
packages/opencode/src/cli/offline.tspackages/opencode/src/index.tspackages/opencode/test/cli/offline.test.ts
| export function local(url: URL) { | ||
| return LOCAL.has(url.hostname) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Classify the complete IPv4 loopback range.
Line 11 allows 127.0.0.1 only. 127.0.0.2 and other addresses in 127.0.0.0/8 are loopback addresses, but this guard rejects them. Allow the full range and add a test for 127.0.0.2.
Proposed fix
export function local(url: URL) {
- return LOCAL.has(url.hostname)
+ return LOCAL.has(url.hostname) || url.hostname.startsWith("127.")
}🤖 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 `@packages/opencode/src/cli/offline.ts` around lines 10 - 12, Update local to
classify every IPv4 address in the 127.0.0.0/8 loopback range as local, not only
the hostname currently covered by LOCAL; add a test verifying that 127.0.0.2 is
accepted while preserving existing classifications.
| export function enable() { | ||
| const original = globalThis.fetch | ||
| const guard = (async (input: RequestInfo | URL, init?: RequestInit) => { | ||
| const request = new Request(input, init) | ||
| const url = new URL(request.url) | ||
| if (local(url)) return original(input as never, init) | ||
| throw reject(url) | ||
| }) as typeof globalThis.fetch |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
cat -n packages/opencode/src/cli/offline.ts
rg -n -C 4 'globalThis\.fetch|local\(|offline' packages/opencode --glob '*.ts' --glob '*.tsx'Repository: bolt-builder/bolt-cli
Length of output: 43988
🏁 Script executed:
#!/bin/bash
set -eu
cat -n packages/opencode/test/cli/offline.test.ts
command -v bun || true
bun --version 2>/dev/null || true
if command -v bun >/dev/null 2>&1; then
bun - <<'JS'
const blob = new Blob(["blob-body"], { type: "text/plain" })
const blobURL = URL.createObjectURL(blob)
for (const url of ["file:///etc/hosts", "data:text/plain,hello", blobURL, "s3://bucket/key"]) {
try {
const response = await fetch(url)
console.log(JSON.stringify({ url, status: response.status, text: (await response.text()).slice(0, 40) }))
} catch (error) {
console.log(JSON.stringify({ url, error: String(error) }))
}
}
URL.revokeObjectURL(blobURL)
JS
fiRepository: bolt-builder/bolt-cli
Length of output: 2068
🌐 Web query:
site:bun.sh/docs fetch file:// data: blob: URL Bun fetch supported protocols
💡 Result:
Bun's fetch implementation supports file://, data:, and blob: URL protocols in addition to standard HTTP and HTTPS requests [1]. 1. file:// URLs: You can use the fetch API to retrieve local files from the filesystem [1]. 2. data: URLs: Bun supports the data: URL scheme, allowing you to fetch data encoded directly within the URL string [1]. 3. blob: URLs: Bun supports fetching blobs, which are commonly created using URL.createObjectURL [1]. These are frequently used to retrieve data from in-memory objects or as a source for Web Workers [1][2]. Additionally, Bun's fetch supports the s3:// protocol for interacting with Amazon S3 buckets [3].
Citations:
- 1: https://bun.sh/docs/runtime/networking/fetch
- 2: https://bun.sh/docs/runtime/workers
- 3: https://bun.sh/docs/runtime/s3
🏁 Script executed:
#!/bin/bash
set -eu
url='https://bun.sh/docs/runtime/networking/fetch'
if command -v curl >/dev/null 2>&1; then
curl -LfsS "$url" | rg -in -C 3 'file:|data:|blob:|protocol|URL' | head -120 || true
fiRepository: bolt-builder/bolt-cli
Length of output: 50378
🏁 Script executed:
#!/bin/bash
set -eu
curl -LfsS https://bun.sh/docs/runtime/networking/fetch |
rg -in -C 3 'file:|data:|blob:|protocol|URL' | head -120Repository: bolt-builder/bolt-cli
Length of output: 50378
Permit local Fetch resource schemes explicitly.
local() checks only url.hostname, so file:, data:, and blob: requests fail in offline mode. Permit these schemes by protocol, keep s3: blocked, and add coverage for all three schemes.
🤖 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 `@packages/opencode/src/cli/offline.ts` around lines 20 - 27, Update the fetch
guard in enable and its local URL validation to explicitly allow file:, data:,
and blob: protocols, while continuing to reject s3: and other non-local
requests. Add coverage verifying offline fetch permits each of the three
resource schemes and keeps s3: blocked.
| test("blocks remote requests immediately", async () => { | ||
| const start = performance.now() | ||
| expect(fetch("https://example.com")).rejects.toThrow("offline mode") | ||
| expect(performance.now() - start).toBeLessThan(100) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/sh
set -eu
file=$(git ls-files 'packages/opencode/test/cli/offline.test.ts')
printf '%s\n' "$file"
cat -n "$file"
printf '\n--- package/test configuration references ---\n'
rg -n --glob 'package.json' --glob 'bunfig.toml' --glob 'vitest.config.*' --glob 'jest.config.*' \
'"test"|bun test|vitest|jest' packages/opencode . 2>/dev/null | head -120Repository: bolt-builder/bolt-cli
Length of output: 4503
🏁 Script executed:
#!/bin/sh
set -eu
printf '%s\n' '--- Offline implementation ---'
file=$(git ls-files 'packages/opencode/src/cli/offline.ts')
printf '%s\n' "$file"
cat -n "$file"
printf '%s\n' '--- Bun version declarations ---'
rg -n --hidden --glob 'package.json' --glob 'bun.lock' --glob 'bun.lockb' \
'bun|`@types/bun`' packages/opencode package.json bun.lock bun.lockb 2>/dev/null | head -160Repository: bolt-builder/bolt-cli
Length of output: 19918
🌐 Web query:
Bun test expect rejects await promise matcher documentation
💡 Result:
In the Bun test runner (bun:test), the.rejects matcher is used to assert that a promise or a function returning a promise rejects [1][2][3]. Usage with Promises and Functions: You can use.rejects to verify that a promise rejects with a specific value or to check if an asynchronous function throws an error using.toThrow [1][4]. Example with a promise: expect(Promise.reject("error")).rejects.toBe("error"); Example with an async function (using.toThrow): await expect(async => { await fetchUser("invalid-id"); }).rejects.toThrow("User not found"); Key Details: - Async execution: When testing functions that throw, ensure you await the expect call [1][4]. - Input types: The.rejects matcher accepts both promise objects and functions that return a promise (or thenable) [5]. - Asymmetric Matchers: Bun also provides asymmetric matchers like expect.rejectsTo, which can be used within.toEqual assertions for more complex matching needs [3][6]. - Jest Compatibility: Bun's test runner is designed for Jest compatibility, so these patterns will be familiar if you have used Jest previously [1][7]. Documentation for these matchers can be found in the official Bun API reference under the bun:test module [8][9].
Citations:
- 1: https://bun.com/docs/test/writing-tests
- 2: https://bun.sh/reference/bun/test/Matchers/rejects
- 3: https://bun.sh/reference/bun/test
- 4: https://bun.sh/docs/test/writing-tests
- 5: bun:test: accept thenables and functions in expect .resolves/.rejects oven-sh/bun#32944
- 6: https://bun.sh/reference/bun/test/Expect
- 7: https://bun.com/reference/bun/test
- 8: https://bun.sh/reference/bun/test/Matchers
- 9: https://bun.com/reference/bun/test/Expect
Await the rejected-fetch assertion.
The asynchronous matcher is not awaited, so the test can finish before it validates the rejection.
Proposed fix
- expect(fetch("https://example.com")).rejects.toThrow("offline mode")
+ await expect(fetch("https://example.com")).rejects.toThrow("offline mode")📝 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.
| test("blocks remote requests immediately", async () => { | |
| const start = performance.now() | |
| expect(fetch("https://example.com")).rejects.toThrow("offline mode") | |
| expect(performance.now() - start).toBeLessThan(100) | |
| test("blocks remote requests immediately", async () => { | |
| const start = performance.now() | |
| await expect(fetch("https://example.com")).rejects.toThrow("offline mode") | |
| expect(performance.now() - start).toBeLessThan(100) |
🤖 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 `@packages/opencode/test/cli/offline.test.ts` around lines 24 - 27, Await the
rejected-fetch assertion in the “blocks remote requests immediately” test so the
test waits for and validates the asynchronous rejection. Keep the existing
offline-mode error expectation and timing assertion unchanged.
Adds a global
--offlineflag. Without a network, outbound calls today hang until their own (often long) timeouts; with the flag, every non-local fetch fails immediately with an actionable message, while loopback and in-process destinations (the daemon,bolt serve, the internalopencode.internalfetch bridge) keep working, and anything already cached on disk (e.g. the models catalog) continues to be served:The guard installs in the yargs middleware and also honors
OPENCODE_OFFLINE=1, which the middleware sets so spawned bolt subprocesses (background jobs, workers) inherit offline mode. Wrapping global fetch covers both the AI SDK provider layer and Effect's fetch-backed HttpClient in one place.Validated with
bun run typecheckandbun test ./test/cli/offline.test.ts(guard blocks remote hosts in under 100ms, loopback passes through, restore works);bolt --offline modelsserves from the disk cache in a sandbox without further network.Every commit touches exactly one file. Pushed with
git push --no-verifybecause the repo pre-push hook segfaults in sandboxes.Summary by CodeRabbit
--offlinecommand-line option and support for enabling offline mode through the environment.