Skip to content

Installing and upgrading

joshdaugherty edited this page Sep 26, 2026 · 2 revisions

Installing and upgrading

Describes robot-council/cli v0.4.27.

Requirements

  • PHP 8.4 or later

exec: php: not found means there is no php on this PATH. The shim ends in exec php, so without one every command fails with .../robot-council/bin/robot-council: line 12: exec: php: not found. Where it was seen, Laravel Herd's bin/php was a symlink to a php84 that no longer existed, until PHP 8.4 was reinstalled from Herd's settings (#319). A harness can also hand its processes a different PATH from your shell; Cursor is the case recorded so far.

Installing

Install it into a directory of its own, as versions side by side behind a launcher, so upgrading never replaces a file a running bridge holds (#280):

composer create-project robot-council/cli "$HOME/.robot-council-setup" --no-dev
php "$HOME/.robot-council-setup/robot-council" upgrade --root="$HOME/.local/robot-council"
rm -rf "$HOME/.robot-council-setup"

On Windows, in PowerShell:

composer create-project robot-council/cli "$HOME\.robot-council-setup" --no-dev
php "$HOME\.robot-council-setup\robot-council" upgrade --root="$HOME\.local\robot-council"
Remove-Item -Recurse -Force "$HOME\.robot-council-setup"

That installs the newest release under ~/.local/robot-council/versions/<version>/, and puts the launcher in ~/.local/robot-council/bin. Put that directory on your PATH, and robot-council is available everywhere, which is what wiring the bridge into a harness assumes. The launcher starts the newest installed version each time a process starts.

Put the launcher's bin ahead of ~/.composer/vendor/bin on PATH if the machine ever had a composer global require robot-council/cli install, or the old copy answers first and nothing says so (#319). Where this was found, both copies were the same version, so robot-council --version could not tell them apart, and a background claude -p started its bridge from the old copy. In ~/.zshrc, put the launcher's export after the Composer one, since each line prepends. Check with which -a robot-council: the first line should end in .local/robot-council/bin/robot-council. Once no session runs the old copy, remove it with composer global remove robot-council/cli. On that machine the running seats were untouched, and a fresh shell found only the launcher.

On Windows, point a harness at the launcher through php: command is php, and args start with the absolute path to ~\.local\robot-council\bin\robot-council.php, then mcp. A harness that starts processes without a shell cannot run the .cmd shim. Never point it at a file under versions\: the harness would go on starting that one version, and fail to start at all once an upgrade has removed it.

Upgrading

robot-council upgrade

It needs nobody at the machine, and it is safe with every session's bridge running, including on Windows. It installs the new version into a directory of its own and removes nothing a running process holds: a running bridge keeps the version it started with, and each session picks up the new one the next time it starts. A session can run it unattended. An old version still in use is kept and removed by a later upgrade, once nothing runs it. robot-council upgrade <version> installs a particular one.

The layout, for reference:

path what it is
bin/robot-council, bin/robot-council.cmd the shims on PATH, for macOS and Linux, and for Windows
bin/robot-council.php, bin/select.php the launcher, which a routine upgrade never replaces
versions/<version>/ one installed version; .in-use in it is locked by every process running it

Moving from a single-directory install

Earlier instructions installed with composer require robot-council/cli into one directory, and upgraded with composer update in place. On Windows, upgrading that way while any bridge is running breaks the install partway: Windows will not delete a file a running process holds open, so Composer removes the old package, fails on the one locked file, and leaves vendor/bin/robot-council answering Permission denied. macOS and Linux allow it, which is why the same command looked safe there.

To move to the versioned layout, run the three commands above with --root naming your existing install directory, then point PATH at its bin, in place of vendor/bin, and ahead of any ~/.composer/vendor/bin, as installing describes. The old files are not touched, so it is safe with bridges running; remove the old vendor/ once no session is using it.

To recover an install already broken the old way: close every session on the machine that runs a bridge from it, including the one that ran the upgrade, whose own bridge holds the lock too. Confirm no php process is still running robot-council mcp. Then, from a plain terminal, run composer install -d <install directory> -- the lock file already names the version you asked for -- check robot-council --version, and relaunch the sessions. Then move to the versioned layout, so it does not happen again.

Use v0.4.2 or later. Every release before it failed the first command that made an HTTP request -- enroll, mcp, and api all stopped at Target class [Illuminate\Http\Client\Factory] does not exist. -- because a package it needs arrived only through a development dependency (#237).

Why not composer global require

A global install shares one dependency graph with every other globally installed tool, and this command line needs Laravel 13: laravel-zero/framework v13 requires illuminate/support and illuminate/collections ^13.24. Any global tool that caps Laravel below 13 makes the two impossible to install together, and a directory of its own cannot have that problem.

statamic/cli before 3.6.4 is one of them. 3.6.1 through 3.6.3 require illuminate/support ^10.0|^11.0|^12.0; 3.6.4 accepts Laravel 13. Composer never names statamic/cli when it refuses. Its refusal names other packages instead -- in the runs recorded here guzzlehttp/guzzle, guzzlehttp/psr7 and illuminate/collections, each fixed to <version> (lock file version) by a partial update, with laravel-zero/framework or illuminate/http as the package requiring them -- and --with-all-dependencies changes that only to … but these were not loaded, likely because it conflicts with another require. While statamic/cli is below 3.6.4, no flag gets past it: -W widens the update to the dependencies of the package being required, and statamic/cli is not one of them.

To install globally anyway, update statamic/cli first, then require this package with -W, because the other packages the two share stay at their locked versions otherwise:

composer global update statamic/cli
composer global require -W robot-council/cli

That works until the next global tool with a Laravel ceiling brings the same failure back, which is why the directory above is the recommendation.

Verified 2026-09-24 on macOS 26.6.2 with PHP 8.4, each in a scratch COMPOSER_HOME. The first set is a copy of a real developer's global composer.json and composer.lock -- statamic/cli ^3.6 locked at 3.6.1, and laravel/cloud-cli ^0.6.1 -- installed from its lock:

step, in order result
composer global require robot-council/cli fails, exit 2
the same with -W --dry-run fails, exit 2, conflicts with another require
composer global update statamic/cli moves it from 3.6.1 to 3.6.4
composer global require robot-council/cli fails, exit 2, other packages fixed … by a partial update
composer global require -W robot-council/cli installs v0.4.2, exit 0

A fresh global set resolved today -- statamic/cli 3.6.4 and laravel/cloud-cli 0.6.1 -- installs with a plain composer global require, exit 0.

The install in a directory of its own was verified beside a global set holding statamic/cli 3.6.1, where a global install fails with exit 2: the single-directory form this section recommended then (composer require robot-council/cli --working-dir=<directory>) resolved ^0.4.2 and exited 0, robot-council --version printed robot-council v0.4.2, and robot-council enroll -v against an unreachable host failed at the network with cURL error 6 rather than in the container. That last step is the one that shows the HTTP client resolved, which list alone does not.

The earlier record was narrower than it read. Of composer global require robot-council/cli, this section said: "Verified 2026-09-22 on macOS 26.6.2 with PHP 8.4, into a throwaway COMPOSER_HOME: that line resolves ^0.2.0, exits 0, writes vendor/bin/robot-council, and robot-council list shows about, api, enroll, mcp, new, and pending." That was true. But an empty COMPOSER_HOME is the one place a conflict with another global tool cannot arise, and list never resolves the HTTP client, so the same run passed on a release that could not make a request. It showed the command installs where nothing else is installed, not that it works on a developer's machine (#236).

Clone this wiki locally