Repository navigation
docs(legal): say what hosts download for projects' images - #16
Merged
Merged
Conversation
Hosts now pull the images projects choose from their registries, and download a project's repository from GitHub to build the image it builds from it, when their owners allow builds (hyperstructure/fctl#16). They also report the images and repositories they allow.
This was referenced Oct 18, 2026
kalaspuff
pushed a commit
to hyperstructure/fctl
that referenced
this pull request
Oct 18, 2026
Finishes #16. A project starts its sessions from an image of its own, or from one that hosts build out of a Dockerfile or devcontainer.json in its repository. Host owners decide which images their machines run and which repositories they build from, and sessions go only to runners that allow their image. The docs now say how to do all of it. Fixes #16 ## What changed, by the issue's points 1. **The contract** (`docs/sandbox-images.md`). "What an image needs" now lists `gh`, a non-root user, both architectures and the oldest Claude Code fctl works with. That is **2.1.101**, the first release that asks its host for fresh OAuth tokens (`oauth_token_refresh`; `remote_control` came in 2.1.63). I found both by checking published Claude Code builds, because the changelog doesn't say. fctl-agent now runs `claude --version` before it starts Claude, and fails the session with a clear message when Claude Code is missing or older. "What fctl writes into a sandbox" is new. The worked example adds .NET rather than Rust, since the default image has had Rust since #45. 2. **CLI.** `fctl projects set` gets `--image`, `--build` / `--build-context`, `--setup-script` (the script, `@FILE`, or `-` for stdin), `--env` / `--unset-env`, and `--cpus` / `--memory` (`default` for the runner's own), with `--json` like the rest. `fctl projects show` prints them. 3. **The image's Claude settings stay.** fctl-agent writes its own keys over `~/.claude/settings.json` instead of replacing the file. It owns the permission mode, `attribution`, `disableClaudeAiConnectors` and the auto-updater. It drops `apiKeyHelper`, `ANTHROPIC_*` and provider variables, and `permissions.disableBypassPermissionsMode`. Plugins, marketplaces, hooks and permission rules stay. 4. **Builds from the repository** (`crates/fctl-host/src/build.rs`). The new project setting `image_build` names a Dockerfile (plus the directory it builds in) or a devcontainer.json. - **When it builds:** a host that allows it reads the file at the tip of the default branch through GitHub's API, using the session's token. It hashes the file, the Dockerfile and the git tree of the build directory, and builds only when no image has that hash. - **How it builds:** it downloads the repository archive (up to 2 GiB) and unpacks only the build directory and the Dockerfile. It sends the token only to GitHub's API origin. Then it runs `docker build` or `container build`, one build at a time. - **Limits:** `build_timeout_minutes` (60) and `max_build_mb` (20 GiB). It keeps each project's newest build, and starts from the last build while GitHub is unreachable. - **Dev containers:** fctl reads `image`, `build.dockerfile`/`context`/`args`/`target`. It leaves out features and lifecycle commands, and says so in the session's log. 5. **Host owners decide what runs.** - `host.toml` gets `allowed_images` (references, `/*` prefixes, digest pins) and `allowed_builds` (`owner/name`, `owner/*`). `fctl host install` gets `--allow-image` and `--allow-build` for them. - Hosts send both as optional hello fields. FleetDO assigns a session only to a runner that runs or builds its image (`fctl_domain::schedule::pick`). A session no runner takes waits and says why ("No runner allows this project's image, …" / "No runner builds this project's image from …"). - The host checks again before it accepts a session. `docs/security.md` says that whoever sets the image, the build or the setup script can read the credentials of the people who start sessions. 6. **The app.** Settings → Sandbox → Image picks each runner's own image, an image from a registry, or a build from the repository. Its hint says which drivers use images. Admin → Runners, `fctl admin runners` and `fctl host status` show each runner's image and what else it runs and builds. 7. **Private registries.** Hosts pull with their own login (`docker login`, `container registry login`), and the docs say so. fctl stores no registry credentials. ADR 0017 records the decisions. One worth a look: `allowed_images` unset still means any image, so upgrading no host stops a project's sessions. ## Compatibility - **New fields:** all optional. `ProjectSettings.image_build` is always serialized, as `null` when there is none. `HostHello.allowed_images` / `allowed_builds` and `Runner.image` / `allowed_*` are skipped when empty. - **Older hosts** send no lists: they run any image and build none, as before. Older agents ignore `image_build`. - **Older clients** (an old web tab, the previous CLI) that send settings without `image_build` keep a project's build, unless they choose an image instead (`settings::keep_unsent`). - **Validation:** image references are now validated with Docker's grammar. - **Images with Claude Code older than 2.1.101** now fail at start with a message. Before, they failed once an OAuth token expired. ## How I verified it - **Rust:** `cargo xtask check` passes (fmt, generated types, Biome and TypeScript, Clippy native and wasm, all tests, the web build). New unit tests cover: - reference parsing and pattern matching, and scheduling by image; - `keep_unsent` and build validation; - JSONC and devcontainer parsing, the build key, tar unpacking (hard links skipped, nothing escapes through links); - the settings merge and the version check. - One host test builds end to end against a fake GitHub (API, plus a second origin for the archive) and a fake `docker`. It covers: build once, reuse, no rebuild after an unrelated commit, rebuild after a change in the build directory or the Dockerfile, removal of the old build, fallback while GitHub is down, a failing build, the size cap, and a devcontainer `image`. It also caught the token going to the archive's redirect origin, which is fixed. - **Web:** Vitest covers the image chooser's state and the runner descriptions. - **Dev stack** (`cargo xtask dev --no-host`, two hosts I registered with the new flags: a process-driver host allowing `ghcr.io/acme/*`, and a Docker-driver host behind a stand-in `docker` that builds from `acme/*`): - a project image no runner allows waited with the reason above; after switching to an allowed image, the waiting session started on the right host and finished its turn; - a build from `other/tool` waited ("No runner builds this project's image from other/tool"); a build from `acme/app` went to the building host, and stopped there only because the dev stack has no GitHub App; - stopping and resuming a session merged seeded "image" settings: plugins, hooks and `RUST_LOG` stayed, `apiKeyHelper` and `ANTHROPIC_BASE_URL` were gone; - the CLI output, `fctl admin runners` and `fctl host status` read as described. - **The .NET example's steps** ran in this sandbox, which runs the default image: SDK 8.0.426 installed, a console app built and ran, and the `profile.d` snippet set the PATH for login shells. - **Docs:** hyperstructure/fctl-site built with these docs, and every link and anchor between pages resolves. - **Independent review:** a separate agent reviewed the whole diff, and b59b708 fixes what it found, with tests for each: - a tag in `allowed_images` allowed that tag pinned to any digest; - reading a large `.json` build file was quadratic; - the host fell back to the last build after any error, not only when GitHub was unreachable; - a Dockerfile that is a symlink would have been read on the host; - Docker Hub prefixes written with `docker.io` matched nothing; - smaller ones: a stale build directory in the CLI, leftover build files after a stop, a signed download link in error messages, a few gaps in reference parsing, and the chooser forgetting typed values. **Not verified:** a real `docker build` or Apple `container build`. This sandbox has neither runtime, so the builder commands, `container image inspect` sizes and `container image delete` are untested on real runtimes. A first build on a Docker host and on a Mac host is worth doing before relying on it. ## Not in this change For a person to decide; I can open issues for them: - Hosts that run only their own image unless their owner says otherwise. - Registry credentials from project secrets. - Dev container features, which need the Dev Container CLI. ## Other repositories - hyperstructure/fctl-site: hyperstructure/fctl-site#16 updates the privacy policy. Hosts now pull the images projects choose, download repositories from GitHub to build them, and report the images and repositories they allow. - hyperstructure/sandbox needs no change: the image's layout is the same, and it ships the newest Claude Code. ## Screenshots Taken at 809b7b2 against `cargo xtask dev` with two dev hosts, 1440 px wide unless noted. Later commits don't change these screens. **Settings → Sandbox, a project that builds its image.** Before, the form has no way to show a build; after, the Image choice, the file and the directory it builds in. | Before | After | |---|---| |  |  | The three choices, an image from a registry with its hint, and the dark theme at 390 px: | Choices | An image | Dark, 390 px | |---|---|---| |  |  |  | **Admin → Runners.** Each runner now says its image, what else it runs and what it builds. | Before | After | |---|---| |  |  |  Session: https://app.fctl.sh/s/ses_01M57YPG1ATK9ARAXTZG43G51J --------- Co-authored-by: fctl-sh[bot] <343971779+fctl-sh[bot]@users.noreply.github.com> Co-authored-by: fctl-sh[bot] <fctl-sh[bot]@users.noreply.github.com>
kalaspuff
deleted the
fctl/39-can-you-check-if-something-is-left-to-do
branch
October 18, 2026 18:23
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 privacy policy says what
fctl hostdownloads and what app.fctl.sh keeps about hosts. hyperstructure/fctl#88 changes both, for hyperstructure/fctl#16:The policy's
updateddate is already today's.Refs hyperstructure/fctl#16
Pairs with hyperstructure/fctl#88.
How I verified it
npm run checkpasses, with the same two warnings asmain.FCTL_DOCS_DIR=<that branch> npm run buildrenders the policy and the docs from the fctl branch. Every link and anchor between the docs pages resolves, including the new sections of "Sandbox images".Session: https://app.fctl.sh/s/ses_01M57YPG1ATK9ARAXTZG43G51J