Skip to content

fix(github): wait for browser callback before polling install status#37810

Open
Harshit16g wants to merge 4 commits into
anomalyco:devfrom
Harshit16g:dev
Open

fix(github): wait for browser callback before polling install status#37810
Harshit16g wants to merge 4 commits into
anomalyco:devfrom
Harshit16g:dev

Conversation

@Harshit16g

@Harshit16g Harshit16g commented Jul 19, 2026

Copy link
Copy Markdown

Issue for this PR

Closes #37786

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

opencode github install hangs indefinitely on Linux and silently times out on macOS/Windows. The root cause is that xdg-open / open / start are non-blocking — they return instantly. The old code immediately entered a 120-iteration polling loop (2 minutes) before the user had even switched to the browser window. On any normal install, the loop exhausted before GitHub's post-install redirect fired.

Root cause:

exec(xdg-open url)                    // returns instantly
do { poll API } while retries < 120  // racing against user switching to browser

Fix — 4 files changed across 2 commits:

packages/opencode/src/util/callback-server.ts (new file)
A minimal, event-driven HTTP server utility. Key design decisions:

  • Binds on 127.0.0.1:0 so the OS assigns a free port — no hardcoded port collision risk.
  • Exposes a promise on the returned CallbackServer object that resolves only when the real browser hits the configured path. No internal polling anywhere.
  • waitForCallback() does Promise.race([server.promise, timeout]) — purely reactive, no setInterval.
  • server.close() is idempotent and rejects the internal promise cleanly if called before the callback fires (e.g. on SIGINT).
  • Serves an HTML success page so the user sees confirmation before the socket closes.

packages/opencode/src/cli/cmd/github.handler.ts (modified)

  • Starts the callback server before opening the browser, so the port is known.
  • Builds the GitHub App install URL with redirect_uri=http://127.0.0.1:{port}/github-install-callback — this is what causes GitHub to redirect the browser back to the local server after the user clicks Install.
  • Replaces the entire do { poll } while (true) loop with await waitForCallback(server, { timeoutMs: 300_000 }).
  • After the callback fires, does a single API check (getInstallation()) to confirm the install is visible server-side.
  • Removes redundant server.close() calls — the server closes itself 500ms after serving the success page.

packages/opencode/test/util/callback-server.test.ts (new file, 6 unit tests)
Covers: port binding, HTML 200 response on correct path, 404 on wrong path without false-resolving the promise, waitForCallback resolving on real browser hit, waitForCallback rejecting after timeout, close() idempotency.

packages/opencode/test/util/callback-server-integration.test.ts (new file, 4 integration tests)
Exercises the full installGitHubApp() flow end-to-end: happy path (server binds → simulated browser redirect → waitForCallback resolves), timeout path (rejects within 1s with no browser hit), wrong-path 404 guard (does not accidentally resolve the promise), and the already-installed bypass (API returns truthy on first check, callback server never started).

How did you verify your code works?

Unit + integration tests — 10/10 pass locally (bun test):

test/util/callback-server.test.ts
✓ server binds on a real OS-assigned port > 0              [32ms]
✓ serves HTML success page and resolves promise            [534ms]
✓ returns 404 for unknown paths — does NOT resolve promise [127ms]
✓ waitForCallback resolves when browser hits the path      [576ms]
✓ waitForCallback rejects after timeout with no browser hit [126ms]
✓ close() rejects the promise and is idempotent            [24ms]

test/util/callback-server-integration.test.ts
✓ full happy path: server binds, browser hits callback, waitForCallback resolves [540ms]
✓ timeout path: waitForCallback rejects and does not hang indefinitely            [184ms]
✓ already-installed path: callback server is never started, no port leak          [1ms]
✓ server returns 404 for wrong paths — does not accidentally resolve              [38ms]

10 pass, 0 fail

Live CLI run on a repo where the app is already installed:

◇  GitHub app already installed   ← exits instantly, no hang
│
◆  Select provider                ← proceeds normally

Confirmed installGitHubApp() short-circuits before the callback server is even created when the app is already installed.

Live CLI run on a fresh repo (new-install path):
The spinner displayed Waiting for GitHub app to be installed — complete it in your browser and held open correctly until the 5-minute timeout — proving the CLI no longer races the user.

Screenshots / recordings

No UI change — terminal-only flow.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Closes anomalyco#37786

Root cause: exec(url) opens the GitHub App install page and returns
immediately, but the polling loop starts BEFORE the user switches to the
browser window. Since xdg-open is non-blocking on Linux, the CLI polls
for up to 120 seconds while the user is still reading the GitHub page —
by the time they install and the redirect fires, the timeout may already
have expired.

Fix: start a short-lived HTTP server on the local machine before opening
the browser. The server listens on /github-install-callback and returns a
friendly 'Authorized!' page. After opening the browser, the CLI waits on
the callback promise (up to 5 minutes) instead of polling. The browser
redirect fires when the user completes installation, resolving the promise
and unblocking the verification poll. The 5-minute timeout ensures the
CLI never hangs forever.

Changes:
- packages/opencode/src/cli/cmd/github.handler.ts: replace do-while polling
  with startCallbackServer() + waitForCallback() before polling
- packages/opencode/src/util/callback-server.ts: new utility providing
  startCallbackServer() and waitForCallback() for browser-based OAuth flows
@github-actions github-actions Bot added the needs:compliance This means the issue will auto-close after 2 hours. label Jul 19, 2026
@github-actions github-actions Bot removed the needs:compliance This means the issue will auto-close after 2 hours. label Jul 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

@Harshit16g
Harshit16g marked this pull request as ready for review July 19, 2026 20:58
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.

[BUG] opencode github install hangs indefinitely after GitHub App installation

1 participant