Skip to content

fix: Add basic auth support to server health checks and reconnection logic#3

Merged
AlaeddineMessadi merged 2 commits intoAlaeddineMessadi:mainfrom
mskoryh:fix-remote-auth
Apr 9, 2026
Merged

fix: Add basic auth support to server health checks and reconnection logic#3
AlaeddineMessadi merged 2 commits intoAlaeddineMessadi:mainfrom
mskoryh:fix-remote-auth

Conversation

@mskoryh
Copy link
Copy Markdown

@mskoryh mskoryh commented Apr 5, 2026

Description:
This PR fixes an issue where the OpenCode client couldn't connect to remote OpenCode servers protected by HTTP basic authentication. Previously, the isServerRunning() function made unauthenticated requests to the /global/health endpoint, causing it to incorrectly report the server as unhealthy when auth was required.

Issue: #4

Changes

  • Modified isServerRunning() to accept optional username and password parameters and include an Authorization header when credentials are provided
  • Updated ensureServer() and waitForHealthy() to propagate auth parameters through the server startup flow
  • Enhanced OpenCodeClient to store credentials and use them during reconnection attempts
  • Fixed the main entry point to pass auth credentials from environment variables
  • Added comprehensive tests for basic auth scenarios

Key Features

  • Backward compatible: Existing code continues to work without changes
  • Secure: Passwords are not logged and remain in memory only
  • Flexible: Supports username/password combinations or password-only with default username "opencode"
  • Full integration: Auth flows through health checks, server startup, and client reconnection

Testing

  • All existing tests pass (319 tests)
  • New tests verify basic auth header generation
  • Integration tested with remote servers requiring authentication

Fixes connectivity issues when using OPENCODE_BASE_URL with protected remote OpenCode servers.

Summary by CodeRabbit

  • New Features

    • Optional username and password can now be provided for server configuration; health checks and auto-start procedures will use HTTP Basic Auth when credentials are present.
  • Tests

    • Health-check and ensure-server behavior now covered by tests verifying Authorization header usage and startup polling with credentials.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 5, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

OpenCode client and server-manager now accept optional username/password. Credentials are stored on the client and propagated into server health checks and startup flows so health probes and auto-start use HTTP Basic Auth when a password is provided.

Changes

Cohort / File(s) Summary
Client + CLI entrypoint
src/client.ts, src/index.ts
Added optional username/password to OpenCodeClientOptions; client stores credentials and passes them into ensureServer during lazy reconnection and initial startup.
Server manager & health checks
src/server-manager.ts
Added username/password to ServerManagerOptions; isServerRunning(...), waitForHealthy(...), startServer(...), and ensureServer(...) accept and propagate credentials; health-checks attach Authorization: Basic ... (defaults username to "opencode" when password present and username omitted).
Tests
tests/server-manager.test.ts
Updated tests to assert fetch health-checks include headers and to validate Basic Auth header behavior for explicit username, default username, and during ensureServer auto-start polling.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client (OpenCodeClient)
  participant Manager as ServerManager
  participant Server as OpenCode Server

  Client->>Manager: isServerRunning(baseUrl, username?, password?)
  Manager->>Server: HTTP GET /health (with Authorization if password)
  Server-->>Manager: 200 OK / unhealthy
  alt Server unhealthy and autoServe
    Client->>Manager: ensureServer({baseUrl, autoServe, username, password})
    Manager->>Server: start binary / poll health (Authorization included)
    Server-->>Manager: becomes healthy
  end
  Manager-->>Client: healthy + version
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Poem

🐰 Hopping proud with creds in paw,
I check each health with careful awe.
A username nibble, password crunch,
The server wakes with carrot lunch.
Secure and spry — let's bounce and run! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding basic auth support to server health checks and reconnection logic, which is the core objective across all modified files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/server-manager.test.ts (1)

270-291: Add one auth regression test for the auto-start polling path.

This test validates the initial check, but not the post-spawn health polling path. Add a case where first check fails, server starts, and subsequent health polls require Authorization to pass.

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

In `@tests/server-manager.test.ts` around lines 270 - 291, Add a new test in
tests/server-manager.test.ts that uses ensureServer to exercise the auto-start
polling path: simulate the initial health check failing (use the existing mock
helper like mockFetchUnhealthy or a custom mock), make the spawn/start path
succeed (mock the spawn or startServer behavior), then have subsequent health
poll responses require an Authorization header to return healthy (use
mockFetchHealthy that asserts the Authorization header). Ensure the test asserts
that ensureServer ultimately returns running: true and version, and that
fetchMock calls for the post-spawn health polls include the Authorization header
in the request options (referencing ensureServer, mockFetchHealthy, and the
spawn/start mock helpers to locate code).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/server-manager.ts`:
- Around line 55-70: The abort timeout (variable timeout) is not cleared if
fetch(url, { ..., signal: controller.signal }) throws, leaving timers
accumulating; wrap the fetch call in a try...finally (or ensure a finally block)
that calls clearTimeout(timeout) and optionally controller.abort() cleanup so
clearTimeout(timeout) always runs; update the block around url, controller,
timeout and the fetch call to move clearTimeout(timeout) into the finally to
guarantee the timer is cleared on success or error.
- Around line 149-154: startServer() still calls waitForHealthy() and
isServerRunning(baseUrl) without passing the new credentials, causing failures
against protected health endpoints; update startServer() (and any startup
polling path) to forward the username and password into waitForHealthy(baseUrl,
timeoutMs, username, password) and to call isServerRunning(baseUrl, username,
password) so auth is used during polling and the protected health check
succeeds.

---

Nitpick comments:
In `@tests/server-manager.test.ts`:
- Around line 270-291: Add a new test in tests/server-manager.test.ts that uses
ensureServer to exercise the auto-start polling path: simulate the initial
health check failing (use the existing mock helper like mockFetchUnhealthy or a
custom mock), make the spawn/start path succeed (mock the spawn or startServer
behavior), then have subsequent health poll responses require an Authorization
header to return healthy (use mockFetchHealthy that asserts the Authorization
header). Ensure the test asserts that ensureServer ultimately returns running:
true and version, and that fetchMock calls for the post-spawn health polls
include the Authorization header in the request options (referencing
ensureServer, mockFetchHealthy, and the spawn/start mock helpers to locate
code).
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 38a6664f-aabf-4260-853b-cf48363c1296

📥 Commits

Reviewing files that changed from the base of the PR and between afa70c4 and 9f94cab.

📒 Files selected for processing (4)
  • src/client.ts
  • src/index.ts
  • src/server-manager.ts
  • tests/server-manager.test.ts

Comment thread src/server-manager.ts
Comment on lines 55 to 70
const url = `${baseUrl.replace(/\/$/, "")}/global/health`;
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 3_000);

const headers: Record<string, string> = {};
if (password) {
const user = username ?? "opencode";
headers["Authorization"] = "Basic " + Buffer.from(`${user}:${password}`).toString("base64");
}

const res = await fetch(url, {
method: "GET",
headers,
signal: controller.signal,
});
clearTimeout(timeout);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Clear the health-check timeout on all paths.

If fetch() throws before Line 70, the abort timer is left pending. This can accumulate timers during repeated polling.

Suggested fix
     const controller = new AbortController();
     const timeout = setTimeout(() => controller.abort(), 3_000);
@@
-    const res = await fetch(url, {
-      method: "GET",
-      headers,
-      signal: controller.signal,
-    });
-    clearTimeout(timeout);
+    let res: Response;
+    try {
+      res = await fetch(url, {
+        method: "GET",
+        headers,
+        signal: controller.signal,
+      });
+    } finally {
+      clearTimeout(timeout);
+    }
📝 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 url = `${baseUrl.replace(/\/$/, "")}/global/health`;
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 3_000);
const headers: Record<string, string> = {};
if (password) {
const user = username ?? "opencode";
headers["Authorization"] = "Basic " + Buffer.from(`${user}:${password}`).toString("base64");
}
const res = await fetch(url, {
method: "GET",
headers,
signal: controller.signal,
});
clearTimeout(timeout);
const url = `${baseUrl.replace(/\/$/, "")}/global/health`;
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 3_000);
const headers: Record<string, string> = {};
if (password) {
const user = username ?? "opencode";
headers["Authorization"] = "Basic " + Buffer.from(`${user}:${password}`).toString("base64");
}
let res: Response;
try {
res = await fetch(url, {
method: "GET",
headers,
signal: controller.signal,
});
} finally {
clearTimeout(timeout);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server-manager.ts` around lines 55 - 70, The abort timeout (variable
timeout) is not cleared if fetch(url, { ..., signal: controller.signal })
throws, leaving timers accumulating; wrap the fetch call in a try...finally (or
ensure a finally block) that calls clearTimeout(timeout) and optionally
controller.abort() cleanup so clearTimeout(timeout) always runs; update the
block around url, controller, timeout and the fetch call to move
clearTimeout(timeout) into the finally to guarantee the timer is cleared on
success or error.

Comment thread src/server-manager.ts
Copy link
Copy Markdown
Owner

@AlaeddineMessadi AlaeddineMessadi left a comment

Choose a reason for hiding this comment

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

Reviewed on commit fa2e716.

The original branch still dropped auth inside startServer() during the auto-start health polling path. I pushed a fix to this branch that:

  • threads username / password through startServer()
  • uses them in waitForHealthy() and the final isServerRunning() version fetch
  • adds a regression test covering the post-spawn auth path

Local verification:

  • focused regression test passed
  • npm test passed (320 tests)
  • npm run build passed

This PR is safe to merge.

@AlaeddineMessadi AlaeddineMessadi merged commit d5b8ffc into AlaeddineMessadi:main Apr 9, 2026
1 check was pending
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants