Repository navigation
fix(xtask): install worker-build when cargo xtask dev needs it - #54
Merged
Merged
Conversation
wrangler.toml builds the Worker with `worker-build --dev`, but only `cargo xtask deploy` installed it, so on a machine without it `cargo xtask dev` built everything and then failed when wrangler started. It now calls util::worker_build() first, as deploy does, and the setup docs list worker-build with what its build needs on Debian and Ubuntu. Fixes #41 Refs #34 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 task done
kalaspuff
deleted the
fctl/24-look-into-https-github-com-hyperstructur
branch
October 18, 2026 11:14
fctl-sh Bot
added a commit
that referenced
this pull request
Oct 18, 2026
Brings in #54, #57 and #58. The conflicts are #57's against #39's: - The daemon starts by retrying until the runtime lists the sandboxes (#57); the background pull of the host's image (#39) starts after that, once the runtime answers. - The hello keeps #57's sandbox limits and #39's resolved image. - session.rs imports both sides' names. With #57's timeouts, a `run` that times out no longer counts as a missing image: it fails at once instead of trying a pull that would hang on the same runtime for up to 30 minutes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kalaspuff
pushed a commit
that referenced
this pull request
Oct 18, 2026
) Every host built the default sandbox image itself with `fctl host image build`, and its Claude Code stayed as old as that build, since fctl turns off Claude Code's auto-updater. #2 asks for CI to publish the image instead, for arm64 and amd64, under a stable name with version tags and labels, rebuilt when Claude Code moves on, with a documented layout that custom images (#16) can build `FROM`; hosts on the default image pull the tag of their own version. The image now lives in a public repository of its own, [hyperstructure/sandbox](https://github.com/hyperstructure/sandbox) (set up in hyperstructure/sandbox#1), which publishes it as `ghcr.io/hyperstructure/sandbox`. This pull request makes hosts pull it and takes the image out of this repository. ## What changed **The image moves to hyperstructure/sandbox.** `images/agent` (the Dockerfile, `provision.sh`, `check.sh`) and the Image workflow leave this repository; hyperstructure/sandbox#1 adds them there, with a README, AGENTS.md, skills, lint and title checks. Its Image workflow keeps what this pull request had built: each architecture builds on a native runner and is checked with `check.sh`, then both are tagged as one image. - `ghcr.io/hyperstructure/sandbox:<version>` for every fctl release, plus `:<major>.<minor>` and `:latest` when that release is the newest they cover. Each is the sandbox's `main` with that release of fctl and the newest Claude Code. - It looks for new fctl releases on fctl.sh every hour, rebuilds the newest release's image as soon as a change to the image merges there, and every day when Claude Code has a newer release or the image is a week old. Only its `main` publishes; its pull requests build and check both architectures. - Labels and index annotations as before: `sh.fctl.version`, `sh.fctl.claude-code.version`, and the commit and date it was built from, which the workflow reads back to decide whether an image is current. The Release workflow no longer starts an image build: its token can't start workflows in another repository, so the sandbox's hourly run notices the release. Why a separate repository: [ADR 0012](docs/adr/0012-the-sandbox-image-lives-in-its-own-public-repository.md) (the image has to be public, and it has a cadence of its own). It's 0012 because #55 adds 0011. **Hosts: `crates/fctl-host`, `crates/fctl`** - `image` in `host.toml` is optional. Unset, sessions start from `ghcr.io/hyperstructure/sandbox:<the host's version>`, so upgrading a host moves its new sessions to the new version's image. `fctl-agent:latest`, which every host installed so far has in its `host.toml`, also means the default, in `host.toml` and in project settings. - The daemon pulls the host's image in the background when it starts and the image is missing. A session pulls a missing image itself. Nothing pulls over an image that is already there, so a local build is kept. - When the version's image can't be pulled (the hour between a release and its image, or a development build), a session on the default image starts from `:latest` and says so in its log. A failure that isn't about the image (the image is there but `run` fails) no longer tries a pull. - Apple's `container image pull` gets `--platform linux/<the host's architecture>`. Without it, Apple's CLI downloads every platform of a multi-arch image (`DefaultPlatform.resolve` in apple/container). - `fctl host image build` downloads an archive of the sandbox's `main` from GitHub and builds its Dockerfile with the host's driver, under the host's image name so sessions use the build, or gives the Lima VM its `provision.sh`. `--source DIR` builds from a checkout instead, to try changes to the image with fctl. The binary no longer carries the files. - CLI: `fctl host image pull` (new, `--json`) pulls the host's image again, for a newer build of its tag. `install` says which image sessions start from, `status` (and `--json`) shows it, `doctor` says whether it's there, and `install --image ""` goes back to the default. - A sandbox without fctl now says in its log that the default image has one, instead of suggesting `fctl host image build`. **Docs:** `docs/sandbox-images.md` (new) covers the image, its tags and labels, how hosts get it, and its layout as an interface for images built `FROM` it: the `agent` user (uid 1000, passwordless sudo), `~/.local/bin`, `~/.cargo/bin`, `~/.bun/bin` and `/opt/fctl/bin` first on the PATH, and the tools. It's the "Sandbox images" page #16 asks for, which #16 can extend; the image's README in hyperstructure/sandbox lists the versions. Getting started, the CLI, self-hosting, security, architecture, development and the top-level README say that hosts pull the image instead of building it. AGENTS.md lists hyperstructure/sandbox among the other repositories (what the host relies on there, and that its Rust follows `rust-toolchain.toml`), and the release and issues skills point to it. The roadmap item is ticked. **Web:** the runner dialog drops step 3 ("Build the sandbox image"), and the project's image field shows `ghcr.io/hyperstructure/sandbox` as its placeholder instead of `fctl-agent:latest`. ## For a person 0.3.0 shipped without this pull request, so the next release (0.4.0) is the first whose hosts pull the image. 1. **Merge hyperstructure/sandbox#1.** Its Image workflow then publishes `ghcr.io/hyperstructure/sandbox:0.3.0`, `:0.3` and `:latest` from the sandbox's `main`, which creates the package. 2. **Make hyperstructure/sandbox public** and apply its settings: the commands are in hyperstructure/sandbox#1. 3. **Make the package public**: the organization's Packages → sandbox → Package settings → Change visibility. A package starts private even when its repository is public, and hosts pull without signing in. The organization's settings must allow public packages. Until then, sessions on hosts of 0.4.0 fail with "ghcr.io/hyperstructure/sandbox:0.4.0 is not on this host and can't be pulled (`fctl host image build` builds it here)". 4. **Merge this pull request**, then hyperstructure/fctl-site#7 once fctl's CI has pinned fctl.sh past it (`gh pr update-branch 7 -R hyperstructure/fctl-site`). ## Compatibility - No protocol or control-plane change. `HostHello.image`, already optional, now carries the image the host actually uses. Hosts pick up the new behavior with the release that includes it; hosts of 0.3.0 and earlier keep building `fctl-agent:latest` from the files in their binary. - `image = "fctl-agent:latest"` in `host.toml` now means the published image. A host that ran a modified image under that name should rename it and set `image`, or rebuild it with `fctl host image build`, which tags it with the default name. - `fctl host image build` needs to reach github.com, as its build already needed Debian's mirrors and the tools' installers. - Hosts keep the images of earlier versions until someone removes them (`docker image rm …`), as they kept the images `fctl host image build` replaced. Removing them automatically, and refreshing a tag when its image is rebuilt, is #47. ## Verification - **The sandbox's Image workflow** ran on hyperstructure/sandbox#1: both architectures built natively (amd64 in 2m56s, arm64 in 2m20s) and `check.sh` passed in each with fctl 0.3.0 and Claude Code 2.1.302 ([run](https://github.com/hyperstructure/sandbox/actions/runs/39123588178)). Its lint (ShellCheck, actionlint) and title checks pass too. - **Its planning step**, run locally against stubbed fctl.sh, downloads.claude.ai and registry responses, decides correctly in 19 cases: a pull request; hourly with no image (build), a current image with an older Claude Code (skip: hourly runs ignore Claude Code), an image built from the last change to the image (skip) and one from before it (build), an index without annotations (build); daily with an older Claude Code or a week-old image (build) and a current one (skip); a push after a change (build); dispatches of 0.2.0 (no `:0.2`: 0.2.1 exists), v0.2.1 (`:0.2`), a current image with an older Claude Code (build), a forced rebuild, another branch (build without publishing), a version fctl.sh doesn't have and a malformed one (errors); and odd answers from fctl.sh and downloads.claude.ai (errors). - **`fctl host image build`** with a stand-in `docker`: `--source` with a checkout builds it with `FCTL_VERSION=0.3.0` and `FCTL_DOWNLOAD=https://fctl.sh/releases`. The download path, pointed at a `git archive` of the sandbox served locally (GitHub's format, with its pax header), unpacks it, builds `…/sandbox-main` and removes the temporary directory; pointed at a real GitHub archive of a public repository, it follows the redirect, unpacks it and says the result has no `provision.sh`; against the real URL while the repository is still private, it fails with HTTP 404. Unit tests cover unpacking a GitHub-style archive and refusing a checkout without `provision.sh`. - **End to end with the stand-in:** an old `host.toml` with `fctl-agent:latest` resolves to `ghcr.io/hyperstructure/sandbox:0.3.0` in `status` and `status --json`; `doctor` reports it "not here yet", then ✓ after `fctl host image pull --json`. - **Unit tests:** the config tests cover unset, empty and old-default images, a custom image and saving. The driver tests use a stand-in `docker`/`container` CLI with an image store and a registry: an image already there, a pull, Apple's `--platform`, falling back to `:latest`, both images missing (with and without a fallback), and a `run` failure that isn't about the image. - `cargo xtask check --quick` passes locally on the move and on the merges with `main` (#48, #50, #52, then #54, #57, #58), and CI's full `cargo xtask check` passes. The merge with #57 keeps its startup retry loop and starts the background pull once the runtime answers; a `run` that times out under #57's limits now fails at once instead of being taken for a missing image and trying a pull. - hyperstructure/fctl-site#7, with its `main` merged, builds against this branch's docs: 8 pages, Sandbox images among them, linking to hyperstructure/sandbox. - Earlier on this pull request, before the move: the publish step's commands against a local registry (one OCI index for both architectures that all three tags point to, with the annotations) and `fctl host install --driver docker` with a stand-in, which writes no `image` and says which image sessions start from. - **Not verified here:** pushing to GHCR with the sandbox workflow's token, creating the package and its visibility. They need `packages: write` on the sandbox's `main`, so merging hyperstructure/sandbox#1 is their test, well before a release depends on them. ## Screenshots Sessions can't attach screenshots yet (#40), so here is what they show. Taken at 7771600 with Playwright against `cargo xtask dev` (before: `main` from a worktree), in both themes and at 390 px: - **Runner dialog, "Bring studio mac online":** before, three numbered steps: 1. Install fctl, 2. Register it as a runner and start the service, 3. Build the sandbox image (once, a few minutes) with `fctl host image build`. After, steps 1 and 2 only, and the note under them reads "It shows up here as online within seconds and pulls the sandbox image in the background. With Lima, prepare the base VM once with `fctl host image build`. `fctl host doctor` checks everything, including whether the Mac is set to never sleep." Both themes and the phone width wrap cleanly. - **Project → Settings → Sandbox:** the Image field's placeholder reads `ghcr.io/hyperstructure/sandbox` (at 7771600 it read `ghcr.io/hyperstructure/fctl-agent`; `main` has `fctl-agent:latest`). Nothing else in the section changes. The move changed only this placeholder's text, which wasn't screenshotted again. Refs #2 Sessions: https://app.fctl.sh/s/ses_01M56C4589JCHJ2W8TXC8V2S4P, https://app.fctl.sh/s/ses_01M56FZDD6WY8J56E3TA0SEMZX for merging #45 and v0.3.0, and https://app.fctl.sh/s/ses_01M57B991V0YYBRJFN0WKEW118 for moving the image to hyperstructure/sandbox --------- Co-authored-by: fctl-sh[bot] <fctl-sh[bot]@users.noreply.github.com> 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.
On a machine without
worker-build,cargo xtask devbuilt everything and then failed when wrangler started the control plane:crates/fctl-control/wrangler.tomlbuilds the Worker withworker-build --dev, and onlycargo xtask deployinstalled it (CI installs it in a step of its own). xtask then kept waiting for the health check, which gives up after five minutes.cargo xtask devnow callsutil::worker_build(), asdeploydoes. It installs worker-build 0.8.7, the version of theworkercrate inCargo.lock, withcargo installwhen it isn't on the PATH. The call comes first, right afternpm ci, so a machine that can't build worker-build finds out in seconds, not after the other builds.docs/development.mdlists worker-build in the setup, with what its build needs on Debian and Ubuntu:pkg-configandlibssl-dev, because on Linux its HTTP client links OpenSSL.I found this while checking #34 on a fresh clone. #33 had already fixed the Claude credential bootstrap that #34 reports, but
cargo xtask devnever got far enough to run it.How I verified it
From a fresh clone (no
.fctl-dev/,crates/fctl-control/.wrangler/,node_modulesorweb/dist), in a sandbox with Rust but no worker-build:main:[custom build] /bin/sh: 1: worker-build: not found, thenRunning custom build 'worker-build --dev' failed, while xtask went on waiting for the health check.opensslrequired by crateopenssl-syswas not found".POST /api/v1/setup,/auth/tokens,/me/claude/token,/projectsand/runnersall return 201, and it reaches "fctl dev is running" in 79 s. Vite serves the app at http://localhost:5173 (npm cifetched the design package). Two fake-claude sessions ran to the end:write notes.mdcommitted a file, andrename Check the dev bootstraprenamed its session withfctl edit..fctl-dev/tokendeleted, it signs in with the password and makes a new token.cargo xtask checkpasses.Not changed
wrangler devexits before it is healthy (any failing first build, a compile error included),cargo xtask devstill waits the full five minutes, becausewait_fordoesn't watch the wrangler process.libssl-dev, was answered by feat: add Python 3.15, Rust, Bun and TypeScript to the sandbox image #45, which added both along withpkg-config.Fixes #41
Refs #34
Session: https://app.fctl.sh/s/ses_01M56GFH2FJV0NW90ZNRFWKEA3