Repository navigation
Conversation
Relay-connected clients now open server-loopback dev servers through the preview gateway at their own https origin, and the Connect tunnel comes back after a server restart. Refs #68. Also proposes the SOCKS-routed preview profiles of #69 (design only, with the Linux WebKitGTK spike). - relay: provision 4 preview hostnames per managed endpoint (<env>-p1..p4: proxied CNAMEs plus ingress rules before the catch-all), found by name so re-linking adds missing ones and deprovisioning removes them before the tunnel; the runtime config carries hostname and previewHostnames. - server: the tunnel runtime publishes its hostnames; a gatewayOpen caller on loopback under the endpoint hostname takes a free preview slot (a live target never loses its slot) and gets origin https://<preview hostname>; new reasons relay-not-provisioned and relay-busy. An outermost main-port layer serves preview hostnames through the slot's target handler, HTTPS-facing (Secure cookie, https Origin rule and Location), never BiBCode's routes, CORS, or admission gate. Preview responses are Cloudflare-CDN-Cache-Control and Cache-Control no-store, service worker scripts are refused, and relay hostnames over plain HTTP redirect to HTTPS. The gateway drain starts when shutdown is signaled and is shared by every caller. - server: restore the Connect tunnel from the stored link at startup, unless the server now listens on another port than the tunnel was provisioned for; link, unlink, and restore share one lock; connect.linkState reports managedEndpoint, previewHostnames, managedEndpointStale, and linkGeneration, and relayConfig refuses a background re-link whose requireLinkGeneration no longer matches. - web: relay resolution (and any server-returned origin) bootstraps on the returned origin and maps it back to localhost per tab; browser mode never frames these previews; the primary client, once authenticated, re-links once per session, silently and fenced, when its tunnel lacks preview hostnames or points at an old port. - docs: remote architecture, user guide, relay README, a relay step in the cross-platform runbook, the #68 spec (with review addenda) and plan, and the #69 proposal. Native relay validation (desktop and browser-mode client over a deployed relay stage) is recorded in the runbook and was not run here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The connector now also starts at boot (tunnel restore), so its own death must reach the Profiler like any other child BiBCode runs (AGENTS.md: Profiler, Crash, and Trace Coverage). - A watcher checks the active connector every 5 s without reaping it. An exit nobody asked for withdraws the relay preview hostnames, logs a warning, reports subprocess.exit and subprocess.crash (Watch::Failures, labelled by the connector's executable), and ends the rest of its process group. It holds the runtime weakly, so a runtime dropped without shutdown still drops its connector. - apply and shutdown report a connector that died on its own since the last check before replacing or stopping it; the Windows liveness probe no longer consumes the process handle. - Relay hostname matching on every main-port request no longer clones or allocates. - Docs: Profiler section, profiler skill events and findings, remote architecture, and a connector-crash step in the relay runbook. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
connect.linkProof compared the proof's loopback origin with the request URI's authority, but an HTTP/1.1 request carries an origin-form target with no authority, so every BiBCode Connect link and background re-link from a real client failed with "Invalid managed endpoint origin." The check now falls back to the Host header, as the auth layer does; tests had passed absolute URIs and never hit the real shape. Also pins the preview-hostname label boundary (a 60-character label keeps its hash whole, a 61-character one shortens its stage prefix), tests that gatewayOpen and relayConfig payloads decode with and without the new optional fields, and corrects the contracts comment on `origin`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b0e1ae648d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| /// Serves a request that reached the main port under this target's BiBCode Connect | ||
| /// preview hostname. TLS ended at Cloudflare, so the client-facing origin is `https`. | ||
| pub async fn serve_https<B: GatewayBody>(&self, request: Request<B>) -> Response<ProxyBody> { | ||
| let _activity = self.0.activity(); |
There was a problem hiding this comment.
Stop unauthenticated traffic from refreshing relay slots
Every request to a public preview hostname calls activity() before the gateway validates a capability or session, and handle_request also touches the target before authentication. Consequently, anyone who learns a preview hostname can request it periodically without a cookie and prevent its target from reaching the 10-minute idle timeout; after four legitimate targets have existed, this can keep every slot occupied and make subsequent opens fail with relay-busy. Track the exchange for shutdown without updating idle activity until bootstrap/session authentication succeeds.
AGENTS.md reference: AGENTS.md:L230-L236
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in a15a09a. Requests without a gateway session no longer touch the target: the per-request activity in serve_https and the touch() at the top of handle_request are gone. Only a bootstrap, or a request whose cookie names a gateway session, counts as activity. That request holds an activity guard through its principal check, so the idle sweep can't close the target under it before the lease takes over (a follow-up local Codex pass caught that window).
Tests in relay.rs:
only_authenticated_requests_keep_a_slot_from_going_idle: a sessionless request no longer keeps the slot, and an authenticated one does.a_session_request_awaiting_its_principal_check_keeps_the_slot: holds the principal check open and runs the idle sweep.
Both failed before the fix. The direct (non-relay) gateway port keeps its per-connection activity; it has no shared slots to exhaust.
| if (isBrowserMode() && tabId !== undefined) { | ||
| return fail({ message: relayFrameMessage(label) }); |
There was a problem hiding this comment.
Route managed-endpoint primary previews to a new tab
When the web UI itself is loaded from the environment's managed endpoint, its connection remains a PrimaryConnectionTarget, so the resolver classifies the public DNS name as via: "host" rather than via: "relay". openLink therefore selects the same-host Browser panel; only after gatewayOpen returns an origin does this branch reject the framed navigation, leaving a normal click at an error instead of opening the promised new tab. Detect this primary-over-managed-endpoint case before choosing the destination, so browser user activation can synchronously reserve the new tab.
AGENTS.md reference: AGENTS.md:L61-L73
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
I don't think this one happens. A page served from the managed endpoint is always HTTPS, because plain HTTP on the environment's hostname gets a 308 to HTTPS. On an HTTPS browser-mode page, canFrameGatewayPreviews() is false (the HTTP gateway would be mixed content), so isPreviewSupportedInRuntime() is false. That makes canUseApp in openLink false: the click takes the system path, openGatewayInSystemBrowser, and reserves the new tab synchronously from the click. The framed refusal in previewGateway.ts is never reached from a click on that page. The HTTPS rule is covered by previewBridge.test.ts ("has no preview host on an HTTPS page"). No change made.
A preview hostname is public, and every request to it refreshed its target's idle clock before any session check, so anyone polling the hostname (or a stale tab's reload client) kept the target, and its relay slot, from ever closing; four such targets left every open failing relay-busy. Now only a bootstrap or a request carrying a gateway session counts as activity. That request holds the target open through its principal check, so the idle sweep cannot close the target under it before its lease takes over. Addresses the Codex review on #91. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. Hooray! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Bump the application manifests and Cargo.lock to 0.9.2 and add the curated v0.9.2 changelog entry covering #88, #89, #90, #93, #94, #95, the PR #91 preview gateway over BiBCode Connect (#68), the skill catalog cache, and the Create PR/MR dialog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
Requested: merge PR #92 and PR #91 and release v0.9.2 for everyone, including everything found in the 2026-10-13 profiling session. Implementation: - #89 remote clients: thread stream patches and coalescing, E2EE compression of large server messages (negotiated, kill switch), and per-method `rpc.bytes` in the Profiler. - #90 PR panel: glab without telemetry, Approve permission reads in one wave, stored-copy paint for GitHub and GitLab. - #88 Git watcher: rescan backoff after overflows; on Linux the worktree watch skips Git-ignored directories and nested repositories. - #93 Claude delivery: an early result no longer ends the new turn; late replays confirm uncertain deliveries. - #94 update relaunch under a systemd unit; Profiler reports coredumps no session covered. - #95 seeded upgrade lanes from any stable tag; Windows relaunch required. - Provider skill catalog cache; hide-worktrees and VCS summary fixes; Create PR/MR dialog; PR #91 preview gateway over BiBCode Connect (#68). Tests: regression tests for every fix (each fails without it); server lib, rpc_wire, provider_claude, desktop lib, vp test, check, typecheck, contracts, release smoke, locked cargo check. Review: Codex after every change until clean. Docs: architecture overview, providers, remote, rpc-and-orchestration, release and Windows testing runbooks, profiler references, CHANGELOG. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
Summary
Clients connected through BiBCode Connect (the relay) can now open dev servers that listen on the server's localhost, through the preview gateway, at their own
https://preview hostname. The Connect tunnel also comes back by itself after a server restart. Closes #68.#69 (SOCKS-routed preview profiles) is a design proposal only, plus a Linux WebKitGTK spike:
docs/superpowers/specs/2026-10-11-preview-socks-profiles-design.md. It ends with three open questions, so #69 stays open.This PR also fixes an older bug that blocked every Connect link from a real client (see Fix below).
Changes
<env>-p1…<env>-p4): proxied CNAMEs, plus ingress rules ahead of the catch-all. Records are found by name, so re-linking adds missing ones and deprovisioning removes them before the tunnel. The runtime config now carrieshostnameandpreviewHostnames.gatewayOpencaller that arrives on loopback under the endpoint hostname takes a free preview slot and getsorigin: https://<preview hostname>. A target with live connections never loses its slot. New error reasons:relay-not-provisionedandrelay-busy.no-storefor both Cloudflare and browsers.connect.linkStatereportsmanagedEndpoint,previewHostnames,managedEndpointStale, andlinkGeneration.relayConfigrefuses a background re-link whoserequireLinkGenerationno longer matches the stored link.localhostper tab. Browser mode never frames these previews.subprocess.crash; it is checked without reaping and terminated by process group.connect.linkProofcompared the loopback origin with the request URI's authority. HTTP/1.1 requests carry only the path, so every real link and re-link failed with "Invalid managed endpoint origin." The check now falls back to theHostheader, as the auth layer already does. Tests had passed full URLs, which is why none caught it. Requests through the tunnel keep their public hostname inHostand are still refused.docs/testing/cross-platform-validation.md, the Internal browser: preview gateway reach for BiBCode Connect (relay) environments #68 spec (with review addenda) and plan, and the Internal browser: SOCKS-routed preview profiles per environment (true-origin previews, Phase 2) #69 proposal.Validation
On
4f60464f, after mergingmain(v0.9.1):cargo fmt --all --checkcargo clippywith-D warningsvp check(0 errors),vp run typecheck,vp run check:contractsOn
b0e1ae64(the link-proof fix and test cleanups):cargo fmt --all --checkcargo clippy -p bibcode-server --all-targets -- -D warningscargo test -p bibcode-server --lib connect_mcp(18 passed; the new origin-form case failed before the fix)cargo test -p bibcode-server --test production_jwt --test production_http_routespreview,relay, andenvironmentHttp(38 passed) and relaydeploymentConfig(13 passed)vp check,vp run typecheckReviews:
vercel-react-best-practices. The re-link component's effect depends on a derived boolean, and a module-level set limits it to one attempt per session.UI.md.Not run:
connector_is_liveviaexited_status) was not compiled locally; this PR's CI compiles it.Profiler coverage
exits::report_exit(Watch::Failures, …), in both the watcher and the apply and shutdown paths.events.mdandfindings.mdand the Profiler section are updated.Follow-ups (documented, not built)
🤖 Generated with Claude Code