Migrate to sbx CLI + publish sandbox image + expand commands + install UX overhaul#21
Merged
Conversation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pass --pull-template never to sbx create so it uses the locally built image instead of attempting to pull from Docker Hub. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add ondrej/php PPA and PHP_VERSION ARG so a single Dockerfile can produce images for PHP 8.3, 8.4, and 8.5. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Matrix builds springloadedco/turbo:php8.3, php8.4, php8.5 on push to main. Uses GHA cache for fast rebuilds. php8.5 is tagged latest. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Watches docker/sandbox-templates in the Dockerfile and opens PRs when it updates. Merge triggers publish-sandbox workflow. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Default image is now docker.io/springloadedco/turbo:php8.4. Users extending the image override via TURBO_DOCKER_IMAGE env var.
When the default springloadedco/turbo image is selected, the install flow skips the build step since sbx pulls it directly from Docker Hub. Custom images still trigger turbo:build.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The build process now pushes to a registry, so the integration test requires registry auth. Renamed env var to RUN_DOCKER_PUSH_TESTS. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Document the springloadedco/turbo Docker Hub image, available PHP tags, and the extend-and-push pattern for custom images.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
sbx uses a separate Docker daemon that only pulls from OCI registries. Add --push to docker build so the image is available to sbx. Remove --pull-template never since images now come from a real registry. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The ondrej/php PPA doesn't support Ubuntu 25.10 (questing) which is what the sandbox base image ships. Simplify to a single image tag using the native PHP 8.4 packages. PHP version tags can be added later when the base image moves to an LTS release. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Symfony Process's setTty(true) does not properly allocate a pty for fully-interactive TUIs like Claude Code — Claude starts then exits immediately with code 137. Replace the PHP process with sbx directly via pcntl_exec so sbx owns the terminal the same way as a direct shell invocation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Running sbx exec via Symfony Process immediately before sbx run via pcntl_exec causes the claude agent to be SIGKILL'd after rendering its welcome screen (exit 137). The prep step leaves the sandbox or sbx daemon in a state that breaks the subsequent interactive session. Move prepareSandbox to run once at sandbox creation (via turbo:install) instead of before every run. Add a turbo:prepare command for manual re-runs when hosts config changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New commands: - turbo:ports - list/publish/unpublish sandbox ports - turbo:stop / turbo:start / turbo:rm - sandbox lifecycle - turbo:exec - run arbitrary commands inside the sandbox - turbo:doctor - health check (sbx installed, sandbox exists, hosts configured, ports published) DockerSandbox additions: - stopProcess(), portsProcess(), publishPortProcess(), unpublishPortProcess() - thin sbx wrappers - execInteractive() - TTY-preserving exec via pcntl_exec (for bash etc.) - execSbx() helper to DRY up pcntl_exec calls CLAUDE.md: authoritative sbx docs references + capability matrix documenting what sbx does NOT support (--env, --add-host, --volume, --publish, --dns, etc.). Also documents the secret injection model, host access patterns, and the pcntl_exec requirement for TUIs. Removed: TurboCommand (unused stub). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
turbo:build was a thin wrapper around `docker build --push` that added only marginal value — users building custom images already know Docker. Worse, the 'dockerfile' config defaulted to the package's Dockerfile, which is the wrong one for extension use cases. Removed: - turbo:build command (DockerBuildCommand) - buildProcess() method from DockerSandbox service - 'dockerfile' config option (TURBO_DOCKER_DOCKERFILE) - getPackageDockerfilePath() helper - Integration test that only exercised buildProcess For custom images, users now run docker build directly: docker build --push -t <image> . turbo:install still prompts for the custom image name but directs the user to run docker build separately before continuing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three improvements to turbo:install:
1. Skill curation
- Remove github-pr-comment (autonomous-workflow artifact)
- Rewrite github-issue to drop ralph-specific example and
feedback-loop placeholders (moved to new skill)
- Update github-milestone example from ralph-specific to generic
Stripe billing example
2. New feedback-loops skill
- Fires on completion claims (done, complete, ready for review,
before commit, before gh pr create)
- Lists the project's configured feedback_loops commands via
{{ $feedback_loops_checklist }} placeholder
- Explicit rule: do not claim done/commit/create PR unless all
commands passed
3. Grouped two-stage skill selection UX
- Skills organized into groups: Laravel, Project, GitHub, Third-party
- Stage 1: multiselect groups (laravel + project default-enabled)
- Stage 2: optional per-skill customization with (installed) markers
- Pre-fills groups based on already-installed skills on re-runs
- New SkillsService::getInstalledSkillNames() helper
4. State-aware Docker sandbox prompt
- Skips gracefully with install hint when sbx not on PATH
- Skips with 'already exists, run turbo:doctor' when sandbox exists
- Only prompts when there's an actual decision to make
- Removes redundant rebuild prompt (user runs turbo:rm + turbo:install)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the two-stage group+customize flow with a single flat multiselect that shows every skill with a group-prefix label: Laravel › controllers Project › feedback-loops GitHub › issue 3rd-party › agent-browser Smart defaults: installed skills checked, plus all skills in default-enabled groups (Laravel + Project). GitHub and 3rd-party groups remain opt-in by default. Fewer screens, everything visible at once, scroll: 15 for longer lists. Planning to follow up with a custom two-pane Prompt later. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Running sbx exec (prepareSandbox → setup-sandbox) immediately before sbx run (plugin marketplace add) triggers the same SIGKILL-on-agent issue seen in turbo:claude (exit 137). Reorder so plugins install before the problematic exec. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the fragile sbx-run-based plugin install with a simple JSON
write. The superpowers plugin is enabled via .claude/settings.json
(a committed project file):
{"enabledPlugins": {"superpowers@claude-plugins-official": true}}
Claude Code reads this on first session and auto-installs the plugin
after the user authenticates via /login. No sbx run needed, no 137
SIGKILL risk, works with OAuth/Max subscriptions.
Removed: installSandboxPlugins() and all sbx run plugin commands.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dead methods removed from DockerSandbox: - ttyProcess() — replaced by pcntl_exec for interactive sessions - runPrompt(), runCommand(), runInSandbox() — were used for plugin install via sbx run, replaced by .claude/settings.json approach Stale references cleaned: - config/turbo.php: removed turbo:build mention in comment - README.md: removed dockerfile config key from example + table, fixed direnv example (turbo build → turbo prompt) - .env.example: removed TURBO_DOCKER_DOCKERFILE Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Remove duplicate/stale step comments in InstallCommand::handle() 2. Fix stale docblock on prepareSandboxProcess() — no longer handles node_modules isolation (removed in c6042a4) 3. Add exit 137 guidance to PromptCommand — if the agent is killed (unauthenticated sandbox), tell the user to run turbo:claude and /login first Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Composer checks package keywords for "dev" and prompts the user to re-run with --dev if they use `composer require` without it. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix turbo:exec wrapping command in bash -c (was passing as single arg) - Remove "Create Docker sandbox?" confirm (sandbox is the whole point) - Install superpowers via npx skills instead of settings.json plugin - Set scoped GitHub token as per-sandbox sbx secret - Fix double sandboxExists() call in DoctorCommand - Replace shell_exec with Process in DoctorCommand and InstallCommand - Inline DisplaysCommands trait into PromptCommand (single consumer) - Update "Docker sandbox" → "sandbox" in command descriptions - Add git insteadOf rule in Dockerfile for SSH→HTTPS rewriting - Add tests for all new commands (Exec, Doctor, Ports, Prepare, Remove, Start, Stop) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Superpowers is a multi-skill package, not a single skill. Pass '*' as the skill name so npx skills installs all 14 skills from the source. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All skill groups (including third-party) are now checked by default on first install. On re-runs, only previously installed skills are preselected — defaultEnabled is ignored once any skill in the group has been installed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- README: superpowers "installed during setup" not "included", add sbx doc links, add superpowers to skills table, "create" not "build" - CLAUDE.md: replace stale plugin marketplace notes with superpowers via npx skills, document GitHub token dual-storage, add git SSH→HTTPS rewriting note - Replace last shell_exec in DockerSandbox::execSbx with Process Co-Authored-By: Claude Opus 4.6 (1M context) <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.
Summary
Migrates Turbo from the deprecated
docker sandboxplugin to the standalonesbxCLI, publishes a pre-built sandbox image to Docker Hub, expands the command surface, overhaulsturbo:install, and simplifies plugin management.Closes #20
What changed
sbx CLI migration
docker sandboxcalls →sbxequivalentspcntl_execfor interactive Claude sessions (Symfony ProcesssetTtydoesn't allocate a proper pty for TUIs — causes SIGKILL exit 137)prepareSandboxmoved from pre-session to install-time only (runningsbx execimmediately beforesbx runfrom PHP kills the agent)Published sandbox image
docker.io/springloadedco/turbo:latest(PHP 8.4, Composer, Node 22, Chromium).github/workflows/publish-sandbox.ymlbuilds + pushes on merge tomaindocker/sandbox-templates:claude-codefor base image updatesturbo:installdefaults to the published image — most users never build anythingFROM springloadedco/turbo:latestDockerfile, build + push with Docker directlyExpanded command surface
turbo:portsturbo:stopturbo:startturbo:rmturbo:exec--ttyfor interactive)turbo:doctorturbo:prepareRemoved:
turbo:build(marginal wrapper overdocker build --push),turbostub command.Install UX overhaul
Group › skilllabels, smart defaults per group(installed), groups with installed skills pre-checkedSkills curation
github-pr-comment(ralph-loop artifact)github-issueandgithub-milestone— removed ralph-specific examples + feedback-loop placeholdersfeedback-loopsskill — fires on completion claims, commit, PR creation. Lists project's configured verification commands via{{ $feedback_loops_checklist }}. Blocks completion until all pass.Superpowers plugin simplified
Replaced fragile
sbx run -- plugin marketplace add(which required the agent running + authenticated, and hit the 137 SIGKILL on fresh sandboxes) with a single JSON write:Claude Code reads this on first session and auto-installs after the user authenticates via
/login.CLAUDE.md expansion
--env,--add-host,--volume,--publish,--dns)host.docker.internal+ policy vs./etc/hosts)pcntl_execrequirement for TUIssbx execbeforesbx runfrom PHPTest plan
composer test— 49 passingcomposer analyse— PHPStan cleancomposer format --test— Pint cleanphp artisan turbo:claudeopens interactive sessionphp artisan turbo:installruns full grouped flow(installed)on re-runsturbo:promptwith authenticated sandboxturbo:ports --publishend-to-endturbo:doctoron fresh vs existing sandboxturbo:exec --tty bashRecommend squash-merge — commit history has iterative discovery churn (add-then-remove cycles) that's useful PR context but doesn't belong in mainline.
🤖 Generated with Claude Code