Skip to content

Migrate captun to published npm 0.0.3#1440

Closed
mmkal wants to merge 2 commits into
mainfrom
migrate-captun-to-npm
Closed

Migrate captun to published npm 0.0.3#1440
mmkal wants to merge 2 commits into
mainfrom
migrate-captun-to-npm

Conversation

@mmkal

@mmkal mmkal commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Moves apps/os off the stale https://pkg.pr.new/captun@14 pin — a pre-merge snapshot of iterate/captun#14, merged 2026-05-23 — onto the published captun@^0.0.3 npm release. This is groundwork for switching local dev tunnelling to captun (see tasks/switch-dev-tunnels-to-captun.md on stream-tui-iterate-cli): future captun features (client reconnect, WebSocket passthrough) will now diff against a real release instead of a frozen PR build.

The captun API changed between the snapshot and the release (captun PRs #16#24), so this is more than a version bump:

// before (@14 snapshot)
const tunnel = await createCaptunTunnel({
  url: `${ingressUrl}/__iterate/intercept-project-egress`,
  headers: { Authorization: `Bearer ${adminToken}` },
  fetch,
});
const { response, tunnel } = acceptCaptunTunnel({ onDisconnect });

// after (0.0.3)
const tunnel = await createCaptunTunnel({
  gateway: `${ingressUrl}/__iterate/intercept-project-egress`,
  token: adminToken, // sent as a ?captun-token= query param — WebSocket clients can't set headers
  fetch,
});
const { response, fetcher } = acceptFetcherCapability({ onDisconnect });

Server-side changes that follow from that:

  • handleCaptunTunnelFetch passes CAPTUN_TOKEN instead of CAPTUN_SECRET (0.0.3 throws a descriptive error on the old key).
  • The project egress-intercept endpoint accepts the admin token via the captun-token query param, falling back from the Authorization header.
  • After accepting an egress-intercept tunnel, the project DO now sends the ready({url}) handshake — 0.0.3 clients block on it with a 5s timeout, so without this every e2e egress-intercept connect would hang.

Verified: apps/os typecheck, repo lint, and pnpm test:project-ingress (6/6, covers the tunnel accept path including the 401 case) on top of latest main. Not verified locally: the e2e helpers need a deployed environment — worth one e2e pass or a benchmark:intercept-tunnel run against a dev/preview env before merging.

History note: this originally landed as 1a0401fc4 on stream-tui-iterate-cli, was reverted there (22d53e2c5), and is cherry-picked here so it can ride to main independently.

🤖 Generated with Claude Code


Note

Medium Risk
Touches admin-authenticated tunnel paths and project egress intercept WebSocket handling; mis-wiring would break e2e tunnels or leave connects timing out without ready.

Overview
Replaces the stale pkg.pr.new/captun@14 pin with published captun@^0.0.3 and updates all call sites for the post-release API.

Client tunnels (createCaptunTunnel in e2e helpers and the intercept benchmark) now use gateway / name / token instead of url plus Authorization headers; auth rides on the captun-token query param because WebSocket clients cannot set headers.

Worker captun relay passes CAPTUN_TOKEN instead of CAPTUN_SECRET. Project egress intercept switches to acceptFetcherCapability (returns fetcher not tunnel), accepts admin auth from captun-token or the bearer header, and calls ready({ url }) so 0.0.3 clients do not hang on connect.

Adds a completed task note documenting verification and follow-ups.

Reviewed by Cursor Bugbot for commit 5d172d6. Bugbot is set up for automated code reviews on this repo. Configure here.

mmkal and others added 2 commits June 10, 2026 13:21
Replaces the pkg.pr.new/captun@14 pin with the published package and its
renamed API (acceptCaptunTunnel -> acceptFetcherCapability etc.).

(Changes made by Misha in the working tree; committed as found so the
branch can merge latest main.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5d172d6. Configure here.

// createCaptunTunnel waits for this before resolving on the client side.
const tunnelUrl = new URL(request.url);
tunnelUrl.search = "";
void tunnel.ready({ url: tunnelUrl.toString() });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ready handshake runs too early

High Severity

fetcher.ready() is invoked synchronously before the WebSocket upgrade response is returned. Captun’s egress-intercept examples defer ready with queueMicrotask so the handshake finishes first; calling it early can prevent the client from receiving the ready message and make createCaptunTunnel hit its ~5s wait timeout.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5d172d6. Configure here.

@mmkal

mmkal commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Closing as a duplicate of #1439 — same code change (branches are identical apart from task-file wording), and #1439 has the review discussion. The Sec-WebSocket-Protocol / token-scoping follow-up from Jonas's comment will be addressed there.

@mmkal mmkal closed this Jun 10, 2026
@mmkal mmkal deleted the migrate-captun-to-npm branch June 10, 2026 12:51
mmkal added a commit that referenced this pull request Jun 10, 2026
Replaces the `merge-to-main-slack` workflow (one Slack message per
merged PR — noisy on busy days) with a workflow that maintains **at most
one message per day** in `#ci`: a one-line PR dashboard summary, with
the full per-PR breakdown in a single threaded reply. Both are created
on the first PR event of the day and updated in place after that.

Channel message:

> **PR dashboard 10th June** — 51 merged · 9 closed without merging · 4
opened · 2 older still open (details in thread)

Threaded reply (rendered from real data):

> **Merged:**
> • [#1410 Fix 5-min logout, deploy-time JWKS, and stream append
skeleton flash](#1410) by jonas
(ad6da76)
> • [#1407 itx: contexts, capabilities, and the one true
handle](#1407) by jonas (f256768)
> …
> **Closed without merging:**
> • [#1440 Migrate captun to published npm
0.0.3](#1440) by misha
> …
> **Opened:**
> • [#1448 Replace per-merge Slack messages with a daily PR
dashboard](#1448) by misha
(draft)
> …
> Old: [#1349](#1349),
[#1355](#1355)

How it works:

- Content is refetched from the GitHub search API on every run (merged /
closed-unmerged / opened-and-still-open today, plus older open PRs), so
the message is self-healing — no incremental state to corrupt.
- The day's message timestamps live in a repo Actions variable
(`SLACK_PR_DASHBOARD_STATE`, `{date, channel, ts, details_ts}`), written
with the same `ITERATE_BOT_GITHUB_TOKEN` the nag workflow uses. No new
Slack scopes needed: `chat.update` uses the `chat:write` the bot already
exercises.
- Targets `#ci`, adopting #1452's decision to move merge announcements
out of `#building` (that PR edited the workflow this one deletes; the
conflict is resolved here by keeping the deletion).
- The threaded details go out as chunked mrkdwn section blocks rather
than one `text` param: on busy days a single text field hits
`chat.update`'s `msg_too_long` (`postMessage` truncates, `update`
rejects — found by e2e-testing against today's ~50 merges).
- Plain-text author names (no @-mentions) since the messages update many
times a day.
- Testable two ways: pushing any `*pr-dashboard*` branch runs it for
real against `#misha-test` with a separate state variable (create,
update-in-place, and threading paths all verified this way — e.g. runs
[27280068182](https://github.com/iterate/iterate/actions/runs/27280068182),
[27288814028](https://github.com/iterate/iterate/actions/runs/27288814028)),
and `node cli.ts github-script
pr-dashboard.update_dashboard.update_pr_dashboard --github-token ...`
does a local dry run that prints both messages.

Task file: `tasks/slack-daily-pr-dashboard.md`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
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.

1 participant