Skip to content

Run the controller on a laptop with just controller-local - #139

Merged
wseaton merged 3 commits into
mainfrom
controller-local
Sep 22, 2026
Merged

wseaton merged 3 commits into
mainfrom
controller-local

Conversation

@wseaton

@wseaton wseaton commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

just controller-local brings up Postgres in podman and runs the controller on loopback with no identity provider, no Vault and no cluster. Every request lands as $USER, an admin, and launches run as local crucible plan run subprocesses. docs/controller-local.md covers it.

The new piece is CONTROLLER_DEV_IDENTITY: an open guard used to resolve every caller to an anonymous viewer, so a controller without auth was read-only. The dev identity only builds on an open guard (which already forces a loopback bind) and boot fails if it is set alongside a token or an issuer.

Driving drafts through the local executor also turned up four breaks, fixed here:

  • draft scripts unpacked 0644 (drafts store no mode; the pod mounts the pack 0755), so ./role.sh hit EACCES
  • FORGE_STORAGE_ROOT was unset, so report tasks read /var/lib/forge
  • HOME/USER/OPENSHELL_PODMAN_SOCKET were stripped from the run env, so the host claude could not find its login and the engine could not find podman on macOS
  • backend = "openshell" was refused as cluster-only, though the engine runs it on the podman compute driver

Also skips the overrides ConfigMap watch when there is no kube config to infer.

Checked against a live local controller: examples/playbook as a draft (completes, verdict valid), a one-task claude agent draft ($0.17, done), and examples/revise-loop on an OpenShell sandbox in podman (two rounds, done). Controller tests for auth, dispatch, drafts, local_run and dispatch_target pass, plus clippy and both modgraph checks.

An open guard (no API token, no issuer) resolved every request to
anonymous, a viewer, so a controller without auth could be looked at but
not driven. CONTROLLER_DEV_IDENTITY names the open-guard caller instead,
drops whatever X-Auth-Request-* headers the client wrote, and refuses to
boot on a guarded deployment where it would name every caller.

Running a draft through the local executor turned up four more breaks:

- A draft stores files with no mode, so its scripts unpacked 0644 and
  failed with EACCES. The pod mounts the pack with default_mode 0755;
  the local run now does the same.
- FORGE_STORAGE_ROOT was never set, so a report task read the pod path
  /var/lib/forge. The local run gets a per-run forge dir.
- run_env stripped HOME and USER, which the host claude CLI needs to find
  its login (the macOS keychain lookup keys on USER). The subprocess is
  the same OS user, so withholding them isolated nothing. It also
  stripped OPENSHELL_PODMAN_SOCKET, the only way the engine finds podman
  on macOS.
- Local mode refused backend = "openshell" as cluster-only. The engine
  boots the sandbox on the podman compute driver by default, and
  examples/revise-loop runs that way through the local controller. An
  openshell pack still needs sandbox_image, now in both modes.

With no inferable kube config the overrides ConfigMap watch is skipped
instead of warning every 30s. The recipe pins KUBECONFIG=/dev/null so a
laptop controller never writes into whatever cluster the shell points at.

Assisted-by: Claude
@github-actions

github-actions Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Docs preview for this PR is built and attached as the docs-preview artifact.

Download docs-preview, unzip, and open index.html in a browser.

Rebuilt for 9cb00df.

@wseaton
wseaton enabled auto-merge September 22, 2026 20:27
Local mode now dispatches openshell packs, so the refusal this test
asserts comes from a pod deployment with no deploy profile.

Assisted-by: Claude
@wseaton
wseaton merged commit 6575038 into main Sep 22, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant