Skip to content

fix(cli): let the TUI wizard create custom relay connections - #3467

Merged
M4n5ter merged 4 commits into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:fix/tui-onboarding-custom-relays
Aug 24, 2026
Merged

fix(cli): let the TUI wizard create custom relay connections#3467
M4n5ter merged 4 commits into
apache:mainfrom
UncertaintyDeterminesYou4ndMe:fix/tui-onboarding-custom-relays

Conversation

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
Contributor

Summary

The TUI setup wizard (/setup and first-run) could not create any of the three custom relay providers — Desktop could. Two gaps, as diagnosed in the issue: the catalog filtered out every provider without a built-in base URL, and the onboarding protocol had no field to carry an endpoint even if they were listed. Per the archaeology in the issue thread, the filter was an explicit phase-1 scope cut in #1254 whose phase-2 base-URL step never landed.

This implements the issue's proposed boundary:

  1. listApiKeyOnboardableProviders() now lists the category: 'custom' relays; requiresBaseUrl tells the wizard to collect an endpoint. Providers whose endpoint is derived rather than user-supplied stay excluded — cloudflare-workers-ai also has an empty registry baseUrl, but it interpolates an account id into a URL template, and Desktop deliberately keeps it out of the base-URL field too; a naive !baseUrl unfilter would have offered it a relay prompt it cannot use.
  2. The wizard gains a base-URL step between provider pick and API key for requiresBaseUrl providers (relays show a 4-step flow, everything else keeps its 3 steps). Input is validated in place with the same rules the Host enforces (http/https, no credentials/query/fragment, 2048-byte cap), so mistakes fail with a readable message instead of a protocol decode error. Blank input is allowed only when a connection already exists — it reuses the persisted endpoint, mirroring the blank-key-reuses-stored-secret pattern.
  3. connection.onboarding.verify/save carry an always-present baseUrl: string | null (exact-record wire style, like apiKey). A non-null value goes through the shared catalog normalizer (normalizeCatalogConnectionBaseUrl; a provider-default value collapses to null). A relay with no endpoint from input, existing connection, or registry is rejected with the new base_url_not_configured reason before any network probe. Since the input shapes and result unions are closed wire schemas, RUNTIME_HOST_COMPATIBILITY_EPOCH moves to 37 so a mixed pair fails the handshake instead of tearing down the session on the first /setup.
  4. Discovery runs against the supplied endpoint, and commitConnectionOnboarding persists it: the journaled onboarding intent gains the field (allowed-but-not-required on read, so an intent journaled by an older build still replays as "no override"), and prepareOnboardingUpsert resolves input ?? previous ?? registry default. Two consistency rules ride along: the no-change early-return also compares the base URL so a URL-only change still commits, and a swapped endpoint drops relayModelProfiles and the last test result — the same endpoint-keyed contract applyConnectionUpdate already enforces, so a new relay does not inherit capability declarations or a "verified" badge from the relay it replaced.

Saving a relay updates the same derived-slug connection Desktop manages, so both surfaces stay in sync.

Fixes #3405

Verification

  • npm --workspace @maka/runtime-host run test — 1064 pass / 0 fail
  • npm --workspace @maka/storage run test — 864 pass / 0 fail (14 pre-existing skips)
  • npm --workspace maka-agent run test (CLI) — 358 pass / 0 fail
  • npm --workspace @maka/desktop run typecheck + tests — clean, 1038 pass (desktop consumes the changed protocol types; it has no onboarding-operation callers)
  • biome check on all changed files — clean; knip output byte-identical to main

New tests (kept deliberately light — five additions, each pinning one contract):

  • protocol: verify/save round-trip with the new field, plus two invalid-URL negatives; the epoch-37 pin alongside the existing epoch ladder
  • coordinator, end to end through real stores: a relay with no endpoint is rejected base_url_not_configured before any probe; a supplied endpoint reaches discovery and persists; a blank re-verify reuses the persisted endpoint; a re-onboarding that swaps the URL persists it and drops the old relay's profile table
  • storage: the onboarding-intent journal round-trips the endpoint, and a journal written by a build that predates the field still replays (crash-recovery compatibility)
  • TUI runner, end to end: filtering to a relay inserts the base-URL step (2/4), a malformed endpoint is rejected in place, and the endpoint threads through both verify and save

An independent adversarial review pass probed the change with running experiments before submission; it surfaced five defects — the missing compatibility-epoch bump, the cloudflare-workers-ai scope leak, stale endpoint-keyed state surviving a URL swap, a missing local byte cap, and two formatter misses — all fixed and re-verified above, with its blank-reuse/crash-replay/normalizer/state-machine probes coming back clean. Details in the review-record comment on this PR.

AI use

Select exactly one:

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Claude Code implemented the fix across the protocol/coordinator/storage/TUI layers, wrote the tests, and ran the verification; an independent adversarial review pass (also Claude) probed the change with fault-injection experiments and its findings were fixed before submission. I reviewed and verified the result.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
Contributor Author

Adversarial review record (pre-submission)

An independent review pass probed the change with running experiments (driven wizard sessions against the built TUI, storage-level probes through the real document stack, protocol decode drills, dist fault injections). It found five defects, all fixed in the submitted head, plus confirmations:

Defects found and fixed

  1. Compatibility epoch not bumped (blocker). baseUrl is required on two closed request shapes and the result unions gained a rejection reason. It demonstrated both mixed-pair directions dying: the frame decode throws inside the session pump and tears the whole Host session down on the first /setup. Fixed: RUNTIME_HOST_COMPATIBILITY_EPOCH = 37, pinned by a protocol test alongside the existing epoch ladder.
  2. cloudflare-workers-ai leaked into the wizard. Four providers have an empty registry baseUrl, not three — Cloudflare's endpoint is an account-id template that Desktop deliberately keeps out of its base-URL field. Verified by driving the real wizard: it landed on a relay prompt it cannot use. Fixed: the catalog lists empty-baseUrl providers only when category === 'custom'; verified the offered set is now exactly the three relays.
  3. A swapped endpoint kept endpoint-keyed state. Re-onboarding relay-a → relay-b retained relayModelProfiles and a "verified" lastTest — state the applyConnectionUpdate path explicitly drops on endpoint change (its comment: the old table must not outlive the relay it described). Demonstrated side by side through the real storage stack. Fixed: prepareOnboardingUpsert derives endpointChanged and feeds it into both the profile branch and the test-basis reset; pinned by an end-to-end coordinator test.
  4. Local URL validation omitted the 2048-byte cap, so an oversized endpoint sailed past the wizard and hit the host's frame decode (fatal per defect 1). Fixed in the wizard's validator.
  5. Two formatter misses the author's earlier check pipeline had swallowed. Fixed.

It also flagged the storage half as untested — two dist fault injections (removing the URL-change commit condition; making the intent field required) survived every existing suite. The submitted head adds a storage intent-journal test (round-trip + legacy replay) and the endpoint-swap coordinator assertions to close the reachable half of that gap.

Confirmed safe (with evidence)

  • Blank-reuse and crash-recovery replay: a blank save preserves the persisted URL; a hand-written legacy journal (no baseUrl key) replays and applies its model change without touching the URL.
  • URL replacement probes the new endpoint while reusing the stored secret, and persists at the next revision.
  • Normalizer boundary: empty/whitespace → null; query/fragment/credentials/ftp/file/javascript/non-string/oversize all rejected; provider-default collapses to null; decode idempotent; the OAuth-override throw is unreachable (OAuth providers never pass providerAuthSupportsApiKey).
  • Wizard state machine: 2/4→3/4→4/4 labels; Esc walks exactly one level (models→key→baseUrl→search); a re-entered URL is what reaches verify and save; errors clear on typing; a late verify result after Esc is dropped; a non-relay picked after abandoning a relay carries no leaked URL.
  • Rejection ordering: a blank key wins over a missing endpoint, and no discovery probe is issued on any rejection.
  • Bypassing the coordinator guard cannot poison the catalog (an endpoint-less relay entry is unusable but loadable).
  • Intent schemaVersion staying at 1 is correct: strict-equality versioning would break the crash-then-upgrade replay this change is designed to support, while a downgrade already fails closed on the unknown field.

Two observations left as-is, for the record: the protocol accepts an endpoint override for any api-key provider (Desktop's settings already allow the same, and the TUI only offers the step for relays); hasConnection is slug-derived, so a Desktop-created relay under a custom slug reads as new in the wizard — pre-existing design, noted since relays are now listed.

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for extending the existing Runtime Host onboarding seam instead of adding a CLI-only connection path. The protocol, validation, journal replay, endpoint-change cleanup, and TUI lifecycle all line up well on this head. One existing-connection identity gap becomes user-visible for custom relays, noted inline.

AI-assisted review disclosure: OpenAI Codex coordinated two independent exact-head review passes. I verified the retained connection-identity path, current CI state, reviews, and mergeability, and I made the final review decision.

Comment thread packages/cli/src/runtime-host-onboarding.ts
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
Contributor Author

Adversarial review record — identity-targeting increment (de62675)

The connection-identity change was probed by an independent review pass with experiments against the real storage stack before pushing. Two defects found and fixed in the pushed head, one pre-existing issue documented:

Fixed:

  1. Epoch 37 reuse (blocker). The earlier commit on this PR minted epoch 37 for the baseUrl shape; adding required connectionId under the same 37 would let two mutually-undecodable frame shapes handshake successfully and then die at frame decode — exactly what the epoch exists to prevent, and epoch-37 builds exist from this PR's own review cycle. The identity shape now takes epoch 38, pinned by its own protocol test.
  2. connection_not_found rendered as a key error. The runner wrapped every verify rejection in "API key 验证失败:…请检查后重新输入" — self-contradictory for a rejection retyping can never fix. Stale-snapshot rejections now render unwrapped.

Verified safe (with running experiments):

  • By-id upsert with a canonical-slug squatter of another provider type: the custom relay edits in place, the squatter is untouched — strictly better than the previous slug_conflict. defaultTarget seeds at the edited connection and a pruned default releases to null rather than dangling.
  • Concurrent delete mid-discovery → connection_not_found, orphan credential swept, no stray connection created — the exact duplicate this change exists to prevent.
  • Wizard state: an abandoned relay pick leaks no connectionId into the next provider's verify; a disabled sole connection re-onboards consistently with the canonical path's existing semantics; retired provider types can never be latched (none are listed).
  • Admission-lane key change (identity when supplied) aligns onboarding with connection.models.fetch's existing keying; the storage lane serializes commits either way, and the CLI is the only connection.onboarding.* client.
  • Protocol negatives: empty/whitespace/oversized/missing connectionId all reject (old-client shapes cannot decode past the epoch gate); the storage-internal target_missing never leaks onto the wire.

Pre-existing, documented not fixed: crash-recovery treats a non-convergent onboarding intent as fatal (invalid_document → the store cannot open). The review reproduced the same brick through an untouched path (canonical row deleted and re-created out-of-band while an intent names the old id), so the class predates this PR; reaching it requires a hard kill plus an out-of-band catalog edit. A follow-up could treat an id/slug-conflicting intent as obsolete and clear it instead of throwing — happy to file an issue if maintainers agree.

@Astro-Han

Copy link
Copy Markdown
Contributor

Hi — this PR conflicts with current main and cannot be merged as-is.

I tested a rebase onto current main locally (in a throwaway worktree — your branch was not touched). It stops on these files:

  • packages/runtime-host/src/__tests__/protocol.test.ts
  • packages/runtime-host/src/protocol/index.ts

These are real source conflicts, so they need your judgement rather than a mechanical rebase — please rebase onto current main and resolve them yourself, then push. Once the branch is conflict-free and CI is green on the new head, I will pick it up for review.

git fetch upstream && git rebase upstream/main
# resolve, then
git push --force-with-lease

Thanks for the contribution — happy to help if any conflict is unclear.


AI-assisted maintenance note, not a review. It does not count as the required human review under CONTRIBUTING.md §Review.

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (e05308be7) and force-pushed with lease; the branch is conflict-free (d1fe0c826).

Both conflicts were the compatibility epoch: main has moved to 39 (37 = catalog search term, 38 = retired execute mode, 39 = Client Capability progress), so this PR's two onboarding epochs collided. Resolution: since neither onboarding shape was ever published separately outside this PR's own review cycle, the baseUrl + connectionId input requirements and the base_url_not_configured / connection_not_found rejections now land together as epoch 40, with one floor test (> 39) added to the existing ladder in protocol.test.ts following main's floor-assertion convention.

Also picked up in the rebase: the new storage test file gained the ASF license header main's audit gate now requires (scripts/asf-license-headers.mjs check is clean on the head).

Local verification on the rebased head (Node 24.18): storage 900 pass / 0 fail, CLI 393/393, desktop 1243/1243 + typecheck clean, biome clean on the full diff vs main. runtime-host's onboarding/protocol suites pass; its host-kernel bounded election does not launch a Candidate… test is flaky on my machine (passes 1 in 3 with drifting actual values — starting/recovering; it exercises #3512's election deadline and this branch does not touch that code). The @maka/ui composer-plus-menu failure on my machine reproduces on sources byte-identical to main (this PR touches neither packages/ui nor packages/core), so both look environmental — CI on this head is the authority; ready for it to run.

@UncertaintyDeterminesYou4ndMe
UncertaintyDeterminesYou4ndMe force-pushed the fix/tui-onboarding-custom-relays branch from d1fe0c8 to 327ed2e Compare August 23, 2026 12:37
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
Contributor Author

Rebased again onto current main (efddab2fb) — main moved fast enough that the previous rebase went stale within hours. New head: 327ed2edf, conflict-free.

Two things changed underneath:

  1. The compatibility epoch advanced to 41 on main (40 = queue entry mutations, 41 = compaction terminal outcome), so this PR's onboarding epoch moves again: both onboarding shapes now land as epoch 42 (same single-epoch reasoning as before — neither was ever published outside this PR's review cycle).
  2. fix(storage): clear obsolete onboarding intent #3571 merged, fixing fix(storage): a non-convergent onboarding intent permanently bricks the runtime-policy store #3566 (crash recovery now clears an obsolete onboarding intent instead of bricking the store). It composes cleanly with this PR — and the interaction is worth stating: fix(storage): clear obsolete onboarding intent #3571 detects the obsolete intent by the exact Onboarding intent conflicts with the connection id error, which this PR's identity-first upsert leaves reachable only in the replay-after-drift case (a journaled id that no longer exists while the canonical slug is held by a different identity) — exactly the case fix(storage): clear obsolete onboarding intent #3571 now clears. A live intent whose connection still exists is found by id first and replays onto its own slug, never hitting that error. The message text is preserved verbatim since it is now a detection contract.

Local verification on the new head: storage 901 pass / 0 fail (includes #3571's new recovery test alongside this PR's journal tests), CLI 405/405, runtime-host onboarding + protocol suites 65/65, biome clean on the full diff, ASF header audit clean. Ready for the CI approval on this head.

@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
Contributor Author

Rebased a third time — main's epoch line is a hot spot and #3602 minted 42 (provider_capacity retry reason) a few hours after my last push, which collided with this PR's 42. New head def0ab4ab: the onboarding shapes now take epoch 43, comment ladder and floor test updated accordingly. One nuance worth flagging for review: this time git auto-merged the epoch constant line (both sides read 42) and only conflicted on the comments — the numeric collision had to be caught and lifted by hand, which is exactly the mixed-shape hazard the epoch exists to prevent.

Verification on this head: clean rebuild, storage 901/0, CLI 406/406, runtime-host onboarding + protocol suites 65/65, biome clean on the full diff, ASF header audit clean.

@Astro-Han this PR only ever conflicts on the epoch line, and each cycle costs a maintainer CI approval — if it goes stale again before review, I'm happy to keep rebasing, but if you have a review window it may be cheaper to look at this head while it's fresh.

@UncertaintyDeterminesYou4ndMe
UncertaintyDeterminesYou4ndMe force-pushed the fix/tui-onboarding-custom-relays branch from 327ed2e to def0ab4 Compare August 23, 2026 14:25

@M4n5ter M4n5ter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

English

Reviewed exact head def0ab4ab9c690f42d080ed17d1bce03f8810ee2: NO-GO — 1×P1 + 1×P2.

  1. [P1] This head's epoch 43 is below current main epoch 44 even though the PR adds required fields and new values to closed onboarding wire shapes. The merge-result guard rejects base=44/head=43 as went backward; this change is not compatible-declaration eligible because old peers reject the new shapes.
  2. [P2] Remote onboarding discovery is not bound to the connection revision, effective endpoint, and credential basis used by the final commit. A supported concurrent policy update can persist relay B/key B with the model inventory discovered from relay A/key A; the production-coordinator reproduction is documented inline.

The protocol/storage/TUI propagation is otherwise feature-proportional and keeps the Host as the persistent authority. The previous custom-slug identity issue is closed on this head; no additional decision-changing issue was found.

Verification: 205/205 focused affected tests passed; Core, Storage, Runtime, Runtime Host, MCP, Eval, and CLI builds passed; Biome and git diff --check passed.

Gate state: the exact-head hosted test is terminal red in an unrelated MCP timing test. The PR also conflicts with current main, so GitHub cannot synthesize a current merge ref and no fresh merge-result gate evidence can be obtained until rebase. Current main is independently red in a Storage compile failure; that baseline failure is not attributed to this PR.

中文

已审查 exact head def0ab4ab9c690f42d080ed17d1bce03f8810ee2NO-GO——1 条 P1 + 1 条 P2

  1. [P1] 当前 head 的 epoch 43 低于当前 main 的 epoch 44,但本 PR 又给闭合 onboarding wire shape 新增了必填字段和新值。merge-result guard 会把 base=44/head=43 判定为 went backward;旧 peer 会拒绝这些新形状,因此本变更不能使用 compatible-change 声明。
  2. [P2] 远程 onboarding discovery 没有绑定最终 commit 所依赖的连接 revision、实际 endpoint 和凭证依据。受支持的并发 policy 更新可以把 relay B/密钥 B 与 relay A/密钥 A 发现的模型清单一起持久化;生产 coordinator 复现已记录在行内 finding。

除此之外,协议、存储与 TUI 的传播复杂度与功能相称,并继续以 Host 作为持久化权威。上一轮 custom-slug 身份问题已在当前 head 关闭;没有发现其他足以改变合并判断的问题。

验证结果:受影响的 focused tests 205/205 通过;Core、Storage、Runtime、Runtime Host、MCP、Eval、CLI 构建通过;Biome 与 git diff --check 通过。

门禁状态:exact-head hosted test 因无关的 MCP 时序测试终态失败。PR 还与当前 main 冲突,因此 GitHub 无法生成当前 merge ref,在 rebase 前无法取得新的 merge-result 门禁证据。当前 main 自身也因 Storage 编译失败而红;这个基线失败不归因于本 PR。

Comment thread packages/runtime-host/src/server/connection-effect-coordinator.ts
Comment thread packages/runtime-host/src/protocol/index.ts Outdated
The setup wizard filtered out every provider without a built-in base
URL — an explicit phase-1 scope cut (apache#1254) whose phase-2 base-URL
prompt never landed — and the onboarding protocol had no field to carry
an endpoint anyway, so the three custom relays were creatable from
Desktop but unreachable from the TUI.

List the category:'custom' relays (cloudflare-workers-ai stays out:
its endpoint is an account-id template, not a user-supplied URL), add
a base-URL step to the wizard between provider pick and API key with
host-mirroring local validation, and thread an always-present
'baseUrl: string | null' through connection.onboarding.verify/save —
exact-record wire style like apiKey, normalized by the shared catalog
rules, rejected as base_url_not_configured when a relay has no endpoint
from input, existing connection, or registry. Blank input on an
existing relay reuses its persisted endpoint, mirroring the blank-key
contract.

Discovery runs against the supplied endpoint and commit persists it:
the intent journal gains the field (legacy journals still replay), the
upsert resolves input ?? previous ?? registry default, a URL-only
change still commits, and a swapped endpoint drops relayModelProfiles
and lastTest — the endpoint-keyed contract the update path already
enforces. The onboarding wire shapes are closed schemas, so the
compatibility epoch moves to 37.

Fixes apache#3405

Generated-by: Claude Code
… relays edit in place

The wizard recognized an existing connection only at the derived
canonical slug, so a relay created in Desktop under a custom slug read
as unconfigured in /setup and saving created a second canonical-slug
connection, leaving the old credential and default target behind.

Onboarding inputs now carry 'connectionId: string | null': the catalog
projection resolves the existing connection (canonical slug first, else
the provider type's sole connection), the wizard threads its identity
through verify/save, the coordinator targets it directly (rejecting a
stale id as connection_not_found instead of duplicating), and the
storage upsert finds the row by identity first, preserving its slug.
A stale-snapshot rejection renders without the retype-the-key framing.

Epoch-37 builds from this PR's own review cycle require baseUrl but not
connectionId, so the identity shape gets epoch 38 rather than reusing
37 for a second mutually-undecodable frame.

Generated-by: Claude Code
…revalidates

Model discovery ran outside the mutation lane and the final commit
re-read latest state, so a concurrent supported policy update could
persist relay B/key B with the inventory discovered from relay A/key A.

Adopt the model-fetch ticket shape: beginConnectionOnboarding locates
the target under the write lane and issues a one-shot WeakMap ticket
whose basis pins the connection revision (covering the endpoint and
every other catalog-visible property), the api-key credential status
plus stored secret from one vault read, and the effective proxy with
its credential — returning the pinned proxy so discovery runs through
the egress the basis certifies rather than re-resolving it. complete
revalidates that basis atomically before the durable intent is written:
drift returns 'superseded' (a new save rejection, riding this PR's
unpublished epoch), a vanished target keeps reporting
connection_not_found, and no journal is written on either.

Verify abandons its ticket (WeakMap-held); save begins its own.
Regression test drives the reviewed race end to end: discovery paused
on relay A/key A, endpoint moved and key rotated concurrently, the save
supersedes with relay B/key B intact and relay A's inventory never
persisted, and a retry commits cleanly.

Generated-by: Claude Code
@UncertaintyDeterminesYou4ndMe
UncertaintyDeterminesYou4ndMe force-pushed the fix/tui-onboarding-custom-relays branch from def0ab4 to bbe7af7 Compare August 24, 2026 02:33
@UncertaintyDeterminesYou4ndMe

Copy link
Copy Markdown
Contributor Author

Both findings addressed on head bbe7af770 (rebased onto main@3bb645e99):

  • [P1] epoch is now 45 (> main's 44), assigned after the rebase per your instruction; floor test and comment ladder updated.
  • [P2] onboarding now uses a begin/complete ticket that binds discovery to the connection revision, credential basis, and pinned effective proxy, revalidated atomically at commit — details and the adversarial verification matrix in the inline reply; the reviewed race is regression-tested end to end.

Local verification: storage 908/0, runtime-host 1123/0, CLI 424/424, desktop typecheck clean, biome + ASF header audit clean. The branch is conflict-free against that main; ready for CI approval on this head.

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Re-review at bbe7af77. One new [P2], filed inline on connection-effect-coordinator.ts. Not blocking beyond that finding — everything previously raised is resolved at this head.

Closed since the last pass (verified at this head, threads resolved):

  • Connection identity — projectProviders() now resolves a Desktop-created relay under a custom slug to the existing connection and plumbs connectionId through verify/save, with tests covering the sole-connection, multi-connection and canonical-wins cases.
  • Discovery/commit basis — the begin/complete ticket pins revision, credential and proxy, so a concurrent policy change supersedes the save instead of pairing a new endpoint with an inventory it never produced.
  • Compatibility epoch — head advertises 45 against current main's 44, monotonic.

CI: test is terminal green on this exact head. Withholding approval only for the open [P2].

中文

bbe7af77 上复审。新增一条 [P2],已作为行内评论提在 connection-effect-coordinator.ts。之前提过的问题在这个 head 上都已解决:连接身份(projectProviders() 现在能认出自定义 slug 下的 relay 并透传 connectionId,且有测试覆盖)、discovery/commit 基准(begin/complete ticket 绑定 revision、凭证与代理)、兼容 epoch(head 45 > 当前 main 44,单调)。对应三条旧 thread 已关闭。exact head 的 test 已终态绿;仅因这条未解决的 [P2] 暂不 approve。

Comment thread packages/runtime-host/src/server/connection-effect-coordinator.ts Outdated
… request customization

The onboarding probe went out on the bare transport fetch, while the
models path wraps it with the connection's custom request headers and
body overlay — so a connection that authenticates through a custom
header verified and fetched models fine but failed re-onboarding.

beginConnectionOnboarding now pins the request-headers secret for the
probe and adds its credential status to the ticket's basis, so a header
rotation between discovery and commit supersedes the save the same way
an endpoint or key change does.

Generated-by: Claude Code

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

APPROVE at exact head bc3d1e1d3603040ab7993e395a90cc6e30512094. The one remaining [P2] is closed; no P0–P3 open.

Scope of this pass: confirmation that the request-customization [P2] from review 5004343688 is genuinely fixed, plus the gate state at this head. This is not a fresh full-surface re-review of all 19 files — the earlier passes covered that, and their findings are closed.

The [P2] is closed, and verified at the source rather than taken on report. That finding was that onboarding discovery passed the raw transport.fetch, bypassing the createRequestCustomizationFetch(headers + bodyOverlay) wrapper that the ordinary models path applies. At this head both paths construct the same wrapper:

  • onboarding — connection-effect-coordinator.ts:263-268: createRequestCustomizationFetch(transport.fetch, { headers: begun.requestHeadersSecret ? parseRequestHeaders(begun.requestHeadersSecret) : {}, bodyOverlay: base.requestBodyOverlay })
  • models — :407-410: the same call shape with prepared.secretMaterial.requestHeaders and prepared.connection.requestBodyOverlay

The onboarding side is the stricter of the two, deliberately so. It reads the header secret pinned on the ticket (begun.requestHeadersSecret) rather than re-resolving it, and the comment above it states why: a flip-and-restore between the two reads would otherwise pass the basis check. That reasoning also covers the proxy (:251-254). Header create/delete/rotation are checked for superseded before the commit, so a rotation landing mid-discovery is rejected rather than silently committed.

What that means for the connections this finding was about: a connection that authenticates through custom request headers now gets those headers on the onboarding discovery request, so re-onboarding no longer fails for it.

Gates at this exact head

  • test — terminal success (run 32696317110)
  • MERGEABLE, non-draft, no stale approvals
  • unresolved review threads — 0 (the last one, PRRT_kwDOSpfFGs6bkiwe, is resolved with this review; it was isOutdated but that only means the anchor line moved, so it was held open until the fix itself was checked)
  • against current main 04836d3b: the synthetic merge tree is clean and the changed files do not overlap main's recent commits

No merge from us. Merging is a maintainer's call.

中文

在 exact head bc3d1e1dAPPROVE,唯一剩下的 [P2] 已闭合,无 P0–P3。

本次范围:只确认 review 5004343688 那条 request-customization [P2] 是否真的修好,以及该 head 的门禁状态;不是对 19 个文件的重新全量审查——那部分由此前几轮覆盖,结论均已闭合。

[P2] 已闭合,而且是回到源码核的,不是照报告采信。 原问题是 onboarding discovery 传了裸的 transport.fetch,绕过了普通 models 路径套的 createRequestCustomizationFetch(headers + bodyOverlay)。当前 head 上两条路构造的是同一个包装:onboarding 在 connection-effect-coordinator.ts:263-268,models 在 :407-410

onboarding 那一侧比 models 更严,而且是刻意的:它用 ticket 上钉住的 begun.requestHeadersSecret,不重新解析;上方注释写明了理由——两次读取之间的"翻转再还原"否则能绕过 basis 校验。同一理由也覆盖了 proxy(:251-254)。header 的新建/删除/轮换在 commit 前会判 superseded,所以 discovery 期间发生的轮换会被拒绝而不是静默提交。

对这条 finding 关心的那类连接意味着什么:靠自定义请求头鉴权的连接,现在 onboarding discovery 请求也会带上这些头,重新 onboarding 不再因此失败。

门禁:test 终态 success(run 32696317110);MERGEABLE、非 draft、无陈旧 approval;未决线程 0(最后一条 PRRT_kwDOSpfFGs6bkiwe 随本 review 关闭——它显示 outdated,但 outdated 只说明锚点行移动,所以一直挂着直到修复本身被核实);对当前 main 04836d3b 的 synthetic merge tree 干净、改动文件与 main 近期提交无重叠。

我们不合并,合并是 maintainer 的决定。

@M4n5ter
M4n5ter merged commit 74dcd56 into apache:main Aug 24, 2026
1 check passed
@Astro-Han

Copy link
Copy Markdown
Contributor

LGTM — merging. The wizard being able to create a custom relay connection closes a real gap; thanks for working through the review rounds on it.

中文

看过了,合并。向导能建自定义中转连接补上了一个实际缺口,感谢配合几轮修改。

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.

fix(cli): TUI onboarding cannot create custom relay connections (OpenAI Chat / OpenAI Responses / Anthropic) — Desktop can

3 participants