Repository navigation
1.11.0 — Claude Code's agent view on every provider, and Codex everywhere under alc --codex claude - #35
Merged
Conversation
Set up compatible tmux through supported system package managers with an opt-out and non-fatal fallback guidance. Add offline Unix and Windows installer coverage and CI jobs, and document dependencies and install options in English and Traditional Chinese. Co-Authored-By: Claude Code <[email protected]>
Under alc --codex claude a full Claude id could still reach chatgpt.com - /model claude-opus-5, a subagent's frontmatter, a fallback chain - and be refused there. The bridge now maps any claude-* id (and the bare aliases) to the most capable, the starting or the cheapest Codex model, and leaves every other id alone so a new Codex model still works on the day it ships. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
One document per provider class - Codex, key-based, keyless local - holding what alc used to export, plus the fable alias and the Claude-only features turned off where the endpoint cannot serve a Claude model. The credential is never in it: apiKeyHelper runs alc claude-credential, quoted for cmd or sh. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
alc reads its arguments by the C runtime's rules, where a backslash just before a quote escapes it. A configuration directory given with a trailing backslash (C:\alc\, or the drive root C:\) therefore swallowed the rest of the apiKeyHelper line, and the helper failed in every session. The trailing run is now doubled inside the quotes, and kept rather than trimmed so C:\ still names the drive root. Both quoted values - alc's path and the configuration directory - go through it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…ce, and only as alc named them write_route now refuses a record whose id is not a route name alc makes, or is not the name of the profile and login the record holds, before any path is built from it. The hub writes records that arrive over its control socket, where a name like ../../x would have put a file outside routes/ and a borrowed name would have moved a route's sessions to another login. The first token is created with create_new, so two starters on a fresh configuration can no longer each mint one and leave a bridge holding the token that lost. The loser waits up to two seconds for the winner to write it and adopts it; a file that stays empty is reported, never replaced. rotate_token still replaces, and only a running bridge calls it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…e each route writer its own temp The bridge's token is now minted by remote::settings::create_token, the way ctl.token is: written in full under a name of its own and linked into place, so bridge.token is never seen empty while it is being written. An empty one is therefore stale and is replaced, where the create_new version left it for every launch and session helper to wait on and then fail. Route files are written with atomic_write's secret temp, unguessable and created exclusively, so two writers of one route no longer truncate each other's temp or find it already renamed away. atomic_write no longer removes the destination before renaming on Windows: rename already replaces it there, and the remove only opened a moment with no file at the path and failed concurrent writers with access denied. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…'s own Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Windows PowerShell 5.1 starts every file it saves as UTF-8 with a byte-order mark, and serde_json refuses one, so a user's own `--settings` file saved that way was refused. One leading U+FEFF now comes off the file's contents, and off JSON passed inline, before alc looks for the opening brace and parses. A path is still opened by its exact name. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
A user's `--settings` whose `env` was null, an array or a string won the merge over alc's whole `env`. The endpoint, the blanked credentials and the model pins were gone, `finish` found no base URL to fill and said nothing, and Claude Code would have sent what alc's apiKeyHelper prints to its own default endpoint. Such an `env` is now refused with the reason, inline or from a file. The values inside an object are left for Claude Code to check. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
One detached process per config dir, loopback only, token on every model request, one route per Codex login, a remembered port it moves off only when another program holds it, and an hour's idleness before it exits - counted until a streaming turn's last byte, not its first. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
A listening socket answers TCP before its server answers HTTP, so from the moment one start binds the remembered port, a second start's hello connects into the backlog and times out while the winner is still building its runtime. Believing that one timeout, the loser took the move path and rotated the token out from under the bridge about to serve - leaving a port held by a bridge every request 401s against for the whole idle hour. A failed bind now asks up to five times, a pause apart, and only AddrInUse is worth asking about at all: PermissionDenied is a range Windows reserves, where nothing is listening. The in-flight guard's whole mechanism was one capture-only statement that no test exercised. Two tests now drive a real two-chunk streaming response through the middleware: in flight after the headers, still in flight before the body is read, released on the last byte, and released too when a client walks away without reading it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
alc claude-credential prints the bridge's token for a Codex route - starting a bridge first when none is up - or a profile's saved key, and nothing else; alc bridge reports and stops the process. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Every Claude launch that leaves Claude Code's own login now carries its provider in a settings document - the fable alias and the Claude-only features included - with the credential behind apiKeyHelper. A Codex launch names a route on the background bridge instead of an in-process adapter, and claude agents takes alc's model and effort after the subcommand. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
A local server ignores the Authorization header and serves only what it has pulled, so Claude Code's own login has nothing to mean there. An Ollama profile set to native auth took the Claude-login path all the same and came away with no settings document - and so with no alias pins, which left haiku, sonnet and opus resolving to Claude model ids that localhost:11434 answers with a 404. It now gets the local document whatever its auth style says, as the pins have always followed the kind. Also puts back the two picker-row assertions that the launch test carried before this feature moved the picker into the settings document: the most capable model is listed first, and no row carries a key the setting's schema does not take. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
prepare finishes the settings document once the bridge is up, writes it by content and puts --settings after the subcommand, or first. Session commands (attach, logs, stop, respawn, rm) go straight to Claude Code. A dry run shows the document and the bridge without writing or starting anything. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The end-to-end run found Claude Code's --name unreachable: alc's guard for its own flags fired after `--` too, and the escape the error suggested hit the same guard. `--` now turns the guard off, which is what a user who typed it meant. The provider-shortcut error says how to hand -p to the agent, and `alc bridge` no longer reports routes as served while it is stopped. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…erywhere Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The paragraph named the settings file between two dashes, and the line break happened to fall so that the second dash opened a line. CommonMark lets a list interrupt a paragraph, so the published page cut the sentence short and rendered its second half as a bullet. Commas do the same job without the trap; the zh-TW page already read correctly. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
`serve_as` matched the bare aliases exactly and otherwise wanted a `claude-` prefix, so `opus[1m]`, `sonnet[1m]` and `haiku[1m]` matched neither: they were relayed to chatgpt.com verbatim, which serves none of Claude's and answers with an error. Claude Code takes those spellings from `/model`, from a subagent's frontmatter and from a `fallbackModel` chain, alc itself uses `opus[1m]` as its fixture for a Claude model the user actually had, and the background-sessions page promises the `[1m]` variants are covered. The suffix asks for a context window on the model named in front of it, so the id is cut at the first `[` before either arm looks at it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The helper Claude Code runs through cmd carried its route unquoted, and the %/" refusal beside it did not cover it. A profile name only passes validate_profile_name on a save, in doctor and in the TUI - never on Store::load - so a hand-edited config.toml reached cmd as written: `my profile` split into two arguments and failed every refresh, `prod%USERNAME%` expanded, and `or&calc` ran calc each time Claude Code asked for the key. A route is `codex-` and twelve hex digits or `profile:` and a valid profile name, and anything else is refused for both shells alike. What survives the check needs no quoting in either. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Two alcs asking for a bridge at once could both start one. Freeing a stale lock and creating it again was three steps, so two starters that judged the same lock dead each removed the other's fresh lock and both came away holding it; and the lock was freed at the ten-second deadline even when the bridge had not answered yet, leaving a moment with neither. The loser then bound nothing, rotated the token and rewrote the settings out from under the bridge that did start, which was left serving a port nothing pointed at. Claude Code's supervisor reviving several background sessions together is enough to cause it. The starter now holds the lock until the bridge answers, and a stale lock is taken over by writing an owner mark over it and reading it back, so only one taker goes on. Only AlreadyExists counts as another alc holding the lock; a read-only run directory says so instead of waiting ten seconds for somebody who is not there. /healthz counted as activity, so anything polling it kept an idle bridge up for ever; only a request that got past the token does now. An empty x-api-key hid a correct bearer token beside it; every credential a request carries is considered. A key with a line break inside it printed two lines from the credential helper however hard its ends were trimmed; it is refused, naming where it was read and never printing it. A settings move that failed on one file abandoned the rest for good; it now rewrites every file it can and reports the ones it could not. The doctor test for the bridge row matched "not running" in the hub's row too, and never asserted the "without calling it a problem" in its name. The one test that talks to a real bridge had no read deadline. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…aches no model starts nothing The default `alc claude` - Claude Code on its own login - was the one launch still carried in the environment, so a session sent to the background came back on Claude Code's default model instead of the one it was launched with, and lost its small model and context window with it. It gets a settings document now like every other launch: the endpoint, the model, the small model and the window, with the cloud-provider switches blanked. The key variables are left out rather than blanked, because no helper answers in their place on this path, and alc still removes a stray one from the process it starts. A user's own --settings is merged into it, as it is everywhere else. `claude mcp list`, `claude doctor`, `claude --version` and the other commands that never open a model connection took the full launch path: a route file, the hour-lived detached bridge, a settings document and a session in the usage ledger. They go straight to Claude Code now, as the session commands do. `ultrareview` too - it runs on Anthropic's servers under Claude Code's own login. `agents` does not: agent view dispatches real work. A dry run printed the bridge's remembered port as though something were listening on it; it says it is the port the bridge keeps. The Codex launch test now reads back the route file the settings document names, which is only read when a request arrives and so could go missing without any other test noticing. And the comments that still described Claude Code on the in-process bridge say where it runs now. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Two sentences the last fix made true. On Claude Code's own login the settings file carries the endpoint and the model and the login answers, so the pages no longer say every session fetches a key through apiKeyHelper. And mcp, doctor, plugin, update and the other commands that never reach a model go straight to Claude Code, starting no bridge and counting no session. English and Traditional Chinese together. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Brings in #34, which treats a keyless-kind profile that has a key as bearer auth, once, in launch::build. It merged without conflict. On this branch the same upgrade also decides Claude Code's settings document, so a keyed vLLM profile gets the credential helper rather than the placeholder token a keyless endpoint is given; the next commit pins that. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…helper #34 tested the upgrade to bearer auth on OpenCode and Codex. Claude Code is where it meets this branch: a keyed profile has to get the keyed settings document, with the key fetched through apiKeyHelper, and never the `alc` placeholder token a keyless endpoint is given - a server started with a key refuses the placeholder, and a background session has nothing but the document to go on. A profile without a key still gets the placeholder. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude Code's agent view on every provider, and every Claude model a Codex model under alc --codex claude. A feature, so a minor release. alc used to hand Claude Code its provider as environment variables pointing at an adapter inside the alc process. Claude Code's supervisor drops those for a background session, so claude agents, claude --bg and ← on an empty prompt all produced sessions that came up on Anthropic's API or not logged in. alc now passes a settings file instead - the one channel Claude Code keeps for a background session - with the credential behind apiKeyHelper, and the Codex adapter for Claude Code runs as a background process of its own (alc bridge): loopback only, behind a token, on a port it keeps, started by the first session that needs it and gone after an hour idle. The default alc claude on Claude Code's own login keeps its model through the background the same way. Under alc --codex claude no request reaches a Claude model: the fable, best and opusplan aliases, [1m] variants, full Claude ids, a subagent's model: and fallback chains are all answered by the Codex model of the same tier, and fast mode and the advisor are off. Verified end to end on Windows against Claude Code 2.1.278 and a real Codex login. `--` now hands Claude Code its own flags, so --name and -p are reachable (alc --codex claude -- --bg --name nightly "..."), and the errors say how. claude mcp, doctor, update and the other commands that never reach a model go straight to Claude Code, starting no bridge and counting no session. Key-based providers now receive their key in both X-Api-Key and Authorization: Bearer, which is how Claude Code sends a helper's credential. alc will not hand a shared Claude Code session to a hub still running 1.10, which would drop the settings and launch it on the wrong provider; stop that hub with `alc hub stop` when its sessions are done. Also in this release: a key saved for a vLLM, Ollama or custom profile is sent at last, so a server started with --api-key stops answering 401 (#34); and the installers set up tmux's optional dependencies. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
The Windows installer probes each tmux on PATH the way alc does at run time, with stdin at end of file. Windows PowerShell's Process writes the console input encoding's byte-order mark into a redirected stdin as soon as the child starts, so on a UTF-8 console (code page 65001) the probe handed tmux three bytes, EF BB BF, instead. Real tmux ignores them, but the offline test's fixture holds the probe to its contract, and failed on the first case both on the Windows runner and locally. The process now starts under an input encoding with no preamble, and the console's own is put back after. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
alc claude agents,alc claude --bg,←and/bgkeep the provider alc gave the session. alc hands Claude Code a--settingsfile instead of environment variables (Claude Code's supervisor drops those), with the credential behindapiKeyHelper=alc claude-credential. The defaultalc claudeon Claude Code's own login keeps its model the same way.alc bridge): loopback, token, remembered port, started on demand, gone after an hour idle. One starter at a time, even when Claude Code's supervisor revives several sessions at once.alc --codex claudeno request reaches a Claude model:fable/best/opusplanare mapped,[1m]variants (includingopus[1m]) are sized at Codex's window, full Claude ids are answered by the Codex model of the same tier, and fast mode and the advisor are off.--now hands the agent its own flags, so Claude Code's--nameand-pare reachable, and the errors say how.claude mcp,doctor,updateand the other commands that never reach a model go straight to Claude Code and start nothing.Test plan
cargo fmt -- --check,cargo clippy --all-targets --all-features -- -D warnings,cargo test --all-targets --all-features(Windows, 711 tests after merging main), Linux clippy from Windowstmux -Vprobe was not getting the empty stdin alc's runtime gives it. Fixed in 451f736.CLAUDE_CONFIG_DIR, no Claude login anywherepong;turnrow ongpt-5.6-sol; bridge self-starteddonein 11 s,ponginclaude logsclaude --settings …restarts it; the new pid survives the session that started itfableandclaude-opus-5both answered bygpt-6-astra; no Claude id in the ledgerfastMode/advisorModelin user settings ignored: no prompt, no Opus←←both answer on Codex; the effort indicator survives/model opusx-api-keyandAuthorization: Bearer, foreground and from the background supervisorcmd /cquoting ofapiKeyHelperholdsThirteen real turns, 131,865 tokens, no Claude model id anywhere in the ledger.
cmd, the bridge start race,/healthzkeeping the bridge alive, commands that reach no model, the default login's model).Compatibility
Key-based providers now receive their key in both
X-Api-KeyandAuthorization: Bearer- how Claude Code sends a helper's credential. alc will not hand a shared Claude Code session to a hub still running 1.10;alc hub stopand retry.Known flake, not from this branch
tests/cli.rs::a_hub_starts_answers_and_stopsfailed once across this branch's full-suite runs on Windows:hub statusreadrun/hub.jsonwhile the stopping hub was deleting it and got "access denied". The hub code is untouched here, and 15 of 15 isolated reruns passed.🤖 Generated with Claude Code