Repository navigation
fix(cli): a running server notices a CLI the installer just added - #778
Merged
Merged
Conversation
…deman Adds packages/desktop, a small native app (Rust: tao, wry/WKWebView, muda, objc2) that opens Codeman in its own window. The universal app is about 5 MB (3.2 MB as a DMG) and runs on macOS 11.3 or later. - Connect screen with two tabs. This Mac finds the local Codeman and offers Open, Start or Install. Start uses launchd for a LaunchAgent install and codeman web -d otherwise. It never starts a second server while one runs, because two servers on one data dir share live sessions. Another machine connects by address (recent servers, Keychain logins) and can install Codeman there over SSH. - Installs run Codeman's install.sh in Terminal from a self-deleting .command script. The installer needs a terminal for sudo, Homebrew and ssh. A local install uses --local --service and the first free port from 3000, and the app opens Codeman once it answers. - The local Codeman's login is read from its LaunchAgent or ~/.codeman/.env. It is sent only to 127.0.0.1 on that port, only while the user's own Codeman process holds it, and is never saved. - Native JS dialogs (alert, confirm, prompt) and HTTP Basic challenge answers, both scoped to Codeman's own pages, so the page keeps working after a server restart. App cookies are cleared before every connect. - Pop-outs through window.CodemanHost, with an allowlist (webview/ never opens a window), external links in the default browser, downloads to ~/Downloads, real full screen and window state that is remembered. - The connect page refuses a WebKit older than Safari 16.4, which Codeman's terminal needs. Codeman side: viewSubagentTranscript shows a toast when the pop-up is refused instead of failing, with zh-CN strings and a test that pins them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…password The This Mac tab runs install.sh --no-admin itself. The two questions only a person can answer move into the app: which coding agent to install, shown only when this Mac has none, and who may reach Codeman. The installer's output goes to a file, so a running install survives the app quitting and is picked up again when it reopens. install.sh gains --ai-cli, --progress and a preflight subcommand, which is what the app reads its choices from, so it keeps no copy of the CLI catalogue. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The agent chooser only appeared during the install, and only on a Mac with no agent. After "Skip for now", or with Codeman already installed, there was no screen for it. The same chooser now works after the install: "Add a coding agent" on the This Mac tab, "Add a Coding Agent to This Mac..." in the app menu (it also works from a Codeman page), and after a Skip the app says Codeman has no agent yet before it opens Codeman. `install.sh agent <id>` installs one agent and nothing else. It checks that THAT agent landed: install_offered_cli is fatal only when no CLI is left, so with another one present a failed install would have reported success. The app runs it as a run of its own (`gui_install::Run::Agent`), in its own temporary folder, so `ps` never reads it as "Codeman is being installed". It may run next to a live server, which an install must refuse, and stops only for another run. `--no-admin` goes to both `preflight` and `agent`: it decides what counts as installable, so the two must agree. CI: the bash 3.2 preflight check never tested anything. Its child run inherited CODEMAN_INSTALL_SH_LIB and stopped at the sourcing guard, and a false [[ ]] does not trip set -e on bash 3.2. Both are fixed for that check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After "Skip for now" there is no other agent, so a failed add hit
install_offered_cli's gate first, and the app showed its first-install text
("re-run the installer") instead of naming the agent and its command. That
is the main path of adding an agent.
run_offered_cli now runs a trusted command and looks again; the gate stays in
install_offered_cli for the install, and `agent` judges its own run. The one
line that executes a trusted command is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A running Codeman remembers a missing CLI for up to 5 minutes, so a status poll never spawns a login shell, and its Run button reads that same memory. So "Run fails, add the agent, Run again" kept saying the CLI was not installed, right after the app had said it was ready. install.sh now touches `cli-installed` in the data dir once a CLI has landed, and resolve() leaves the backoff when that file's mtime changes. It is one stat, inside the backoff branch, and buys one run of the chain, so a resolved CLI never pays for it and a CLI that is still missing goes back to waiting. Re-walking PATH during the backoff would have found hand installs too, but it would put a synchronous stat of every PATH entry on the request path, where one dead network mount freezes the server (#516), and execute candidates that were already refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ark0N
pushed a commit
that referenced
this pull request
Nov 8, 2026
…coding model on this Mac (experimental) Conflicts resolved as on test/desktop-macbook: config/test-suites.ts keeps both browser tests (desktop-connect-page and local-model-ui), and the CLAUDE.md state-files line keeps #778's cli-installed entry with the local model's local-model.json and local-model/ entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
The problem
A running Codeman remembers that a coding agent is missing, for up to 5 minutes after it last looked. That is there for a good reason: the last step of the lookup spawns a login shell, and without the memory every status poll would freeze the server for up to 5 seconds.
The Run button reads that same memory. So this sequence, the most likely one for a new user, goes wrong:
install.sh agent <id>or Codeman Desktop's Add a coding agent (feat(desktop): install Codeman from the app, with no Terminal and no password #773).Restarting the service would clear it, but a tool that adds an agent has no business restarting a server with sessions on it. The only code that cleared the memory is the CLI management routes in Settings, which are off by default.
The fix
The installer leaves a note, and the server reads it.
install.shtouches~/.codeman/cli-installedonce an agent has landed, and for an agent it finds already there, since that one may have been installed by hand a minute ago. The file is empty. A failed install writes nothing.resolve()insrc/utils/cli-executable-resolver.tsleaves its backoff when that file's mtime changes.The change buys one run of the lookup, so an agent that is still missing goes back to waiting. Each agent keeps its own resolver, so each one notices separately.
Why this and not a re-scan
The obvious alternative is to re-walk PATH during the backoff, skipping only the login shell. It would also find agents installed by hand, but it was rejected:
dsh, because some resolvers run the binary to check what it is.This check is one
statof one file in the data folder, which the server reads constantly anyway. It sits after the cached-success return, so a resolved agent never pays for it.Stacked on #773
The base is
feat/graphical-installer, because the only thing that really needs this,install.sh agent, is in #773 and is not onmaster. Onmasterit would be a reader with almost nothing writing to it, plus a conflict ininstall.sh. Merge order: #763, #773, then this one. GitHub retargets it when its base merges.The diff is one commit, and it merges cleanly with the local branch that combines #763's unpushed review commits with #773. Against
masterit adds no conflict of its own. #773 has one, a single line ofinstall.sh --helptext that a later fix onmasteralso changed, which this PR neither causes nor solves.It also changes the wording on the desktop app's last screen, because "within a few minutes" is no longer the normal case.
No changeset: COM writes it.
Tested
On a real Mac (Apple Silicon, macOS 27.2, stock
/bin/bash3.2). A real install of this branch with no agent, under a throwaway home folder, with its server running and already remembering every agent as missing. Then real agents, through the same Rust code the desktop app calls:claude --version:2.1.318 (Claude Code).In the test suites. New tests in
test/cli-executable-resolver.test.tsandtest/install-sh-invariants.test.ts, and the marker's name is pinned on both sides. Each part of the fix was broken on purpose to prove a test catches it, which is how one test that could not fail was found and fixed.Known limits
npm install -g @openai/codexin a terminal writes no marker, so it waits out the backoff as it always has. Runninginstall.sh agent codexafterwards clears it.CODEMAN_INSTANCEhas its own data folder, and the installer only writes under~/.codeman, so a beta instance waits out the backoff too.