shipyard
Delivery conveyor for Claude Code and the OpenAI Codex CLI (investigate → decompose → deliver), plus the reproducible container it runs in — mechanical gates, worktree-per-ticket, PRs driven to green.
Shipyard
A delivery conveyor for coding agents: it takes a problem from "we should look
into this" all the way to a set of green PRs — deep investigation → a validated
ticket graph → a worktree and PR per ticket, babysat to green.
It runs on two runtimes from one source of truth. The Claude Code plugin
(plugins/delivery-pipeline/) is canonical; a generator emits the Codex-native
artifacts from it, so the OpenAI Codex CLI runs the same conveyor and the two
cannot drift. Everything the conveyor must not improvise — git, gh, the graph,
the gates — lives in tested Node/bash scripts shared by both.
Three ways to run it:
| | What you get | Start here |
|---|---|---|
| Container (Claude Code) | The whole toolchain pinned and baked: Claude Code CLI, gsd-core, shipyard, MCP servers, auto-route hook. Isolated, throwaway, bypassPermissions is safe inside it. | [make dev](#quick-start) |
| Host Claude Code | The conveyor and its gates in your own Claude Code, no container. Installable straight from GitHub: claude plugin marketplace add serhii-nochevnyi/shipyard. | [Installing the conveyor into host Claude Code](#installing-the-conveyor-into-host-claude-code) |
| Host Codex CLI | The same skills, subagents and gates, generated for Codex. | [Shipyard on the OpenAI Codex CLI](#shipyard-on-the-openai-codex-cli) |
The container is Claude Code only — Codex is a host-side install. Both host paths
leave everything outside shipyard's own files untouched.
Quick start
This is the containerized Claude Code path. For a host install (Claude Code or
Codex CLI) skip to [Shipyard on the OpenAI Codex CLI](#shipyard-on-the-openai-codex-cli)
or [Installing the conveyor into host Claude Code](#installing-the-conveyor-into-host-claude-code).
``bash`
make dev
make dev runs the guided launcher (scripts/dev.sh): it creates .env if/workspace
missing, builds the images if needed, starts the container, and then asks in turn
whether to log in to Claude, authenticate MCP servers (e.g. Atlassian Rovo), and
which project to work in — clone a new repo into or pick an existingclaude
one there. It finally attaches a or bash session **inside that project
directory**. Each step is skipped if already done, so it is safe to re-run.
Inside this isolated container, Claude always runs without per-action permissionpermissions.defaultMode: "bypassPermissions"
prompts: the image bakes into~/.claude/settings.json, and the entrypoint pre-seeds the one-time bypass/workspace
acceptance plus per-directory trust for and every project inside itshipyard-trust
(the helper covers repos cloned later). So a bare claude fromclaude --dangerously-skip-permissions
any shell behaves the same as (whichmake claude and the launcher's "claude" option still pass explicitly). Use this
only in the throwaway container, never against your host shell.
Individual targets if you prefer to drive it yourself:
| Command | Does |make up
|---|---|
| | start the persistent container (docker compose up -d; builds the image first if missing) |make claude [DIR=subdir]
| | attach a claude --dangerously-skip-permissions session (in /workspace/subdir if DIR is given) |make shell [DIR=subdir]
| | attach a bash shell (in /workspace/subdir if DIR is given) |make clone REPO=<git-url>
| | clone a repo into /workspace (set WORKSPACE_SUBDIR=name to rename) |make bootstrap-atlassian-oauth
| | authenticate the Atlassian Rovo MCP server |make run-docker
| | one-off ephemeral session (docker compose run --rm) |make clean-cache
| | prune MCP server logs older than 7 days from .cache-home |
Delivery workflow
The conveyor is the same on every runtime — only the invocation differs:/shipyard:<cmd> on Claude Code, $shipyard-<cmd> on Codex. This section uses
the Claude Code spelling. You do not have to remember which entry to use —
describe the work and the router picks it:
`text`
/shipyard:route "scope of the work"
It is read-only and advisory: it sizes the work and hands off to the right loop/shipyard:bench
(and the auto-route hook — baked into the container, installable on either host
runtime — surfaces it for you, so in practice you just state what you want). Large multi-ticket efforts go through the three conveyor loops
below; a small change, an existing ticket, or explicit "no ticket" goes to, which implements directly in the current worktree and never
creates a branch, PR, or commit unless you ask.
`text`
/shipyard:investigate "тема або проблема"
Deep investigation: an intake interview refines the problem, parallel research/shipyard:investigate
agents draft options/constraints/risks, then you close open questions and lock
decisions in a dialog. Re-run anytime — it picks up the
open investigation from its artifacts. When all questions are closed it
generates an ADR package (Gate 1 — the only fully human gate).
`text`
/shipyard:decompose
Finds undecomposed ADRs, runs the GSD planning chain under the hood, stampsscripts/validate-graph.cjs
tickets with branches/risk, validates the dependency graph (Gate 2 — automatic,
mechanical), and shows you the ticket set for approval. Gate 2 is exiting 0 and nothing else.
`text`
/shipyard:deliver
Cold-starts from live GitHub state, shows a ticket board (ready /
branched-needs-pr / blocked / pr-open / merged), lets you pick the scope to take
on, then runs each ticket in its own git worktree to its own PR and babysits
every PR to green: CI fixes, review-comment handling, architecture conformance,
with CodeRabbit/Copilot re-review after every push. It only comes back to you for
high-risk approvals, escalations, and merges. Gaps of days between the three
stages are fine — each command re-derives its state from artifacts and GitHub,
not from the chat.
When the run may stop is code, not judgement. state-sync ends every boardfixpoint: NO — 12 item(s) are
with the actionable front and a verdict —
actionable RIGHT NOW or fixpoint: YES — computed byscripts/front.cjs (also runnable alone, --json for the machine view, written to.planning/graph/delivery-front.json). A PR waiting on CI counts as "not a
fixpoint" but never as a reason to block: the run serves the rest of the front and
only waits when that PR is the last thing left.
A phase can span repositories. A ticket whose files live in a sibling repodelivery.repo: owner/name
declares in its plan; every GitHub query, epic branchT-06-01@acme/webapp
and PR is then scoped to that repo, and the board tags it ().pipeline.repos: {"acme/webapp": "/abs/path"}
Tracking needs nothing else; executing there needs a local checkout —. Without the declaration thepending
conveyor watches the wrong repository: a PR merges next door while the board says and every dependent stays blocked. Gate 2 warns on that signature.
Full specification: docs/gsd_multilevel_delivery_pipeline.md.
Agent model policy
The conveyor routes agents by role × risk × attempt, and that policy is code:
`bash`
node plugins/delivery-pipeline/scripts/pipeline-config.cjs model executor --risk high
node plugins/delivery-pipeline/scripts/pipeline-config.cjs model ci-fix --json --attempt 3
node plugins/delivery-pipeline/scripts/pipeline-config.cjs resolve # effective config
It only ever emits the tier aliases opus, sonnet, haiku, fable — the values--json
the Agent tool accepts — and with it also returns the reasoning effort,
which follows the resolved tier (GSD's ladder: light→low, standard→high,
heavy→xhigh). So escalating a repair to the top tier makes it think harder too.
fable is Claude Fable 5: Opus-tier with a 1M-token context window and
adaptive thinking. It is the only alias that expresses "top tier with 1M context".
It is a paid model, so it is never a default — opt in per role:
`json`
{ "pipeline": { "models": { "integrator": "fable", "arch-review": "fable" } } }
Configuration lives in .planning/config.json under two namespaces:delivery_pipeline.* (the capability's own declared config — GSD-native, settablepipeline.*
and validated through GSD's tooling, and it wins) and (shipyard'spipeline
runtime knobs; note is not a valid GSD config key, so edit the filemodel_policy
directly). Keys: (GSD's own budget/quality names work asmodels
aliases), , effort, max_attempts, pr_fetch_limit,integration_mode, use_workflow, graph_gate, jira, repos.
The conveyor also obeys GSD's own settings rather than second-guessing them:git.base_branch decides where epics are cut from and where the integration PRgit.branching_strategy
goes (it outranks the repo default), must stay noneruntime
because the conveyor owns branching, and decides effort clamping.state-sync echoes the effective settings and warns about anything not in effect.
Shipyard on the OpenAI Codex CLI
Codex is a first-class runtime, not a port: the same conveyor, the same
deterministic scripts, the same blocking gates. It is a host-side install,
separate from the Docker image (which is Claude Code only).
The canonical source stays the Claude pluginplugins/delivery-pipeline/commands/*.md
(); a generator emits the Codex-nativedeliver
artifacts from it, so the two runtimes never drift — change a command once and
re-run the installer. Where the runtimes genuinely differ, the conveyor adapts
instead of pretending: Codex has no Workflow tool, so runs its built-inagent_skills
agent path, and needs the bare skill form (both spelled out below).
Prerequisite — gsd-core installed for Codex:
`bash`
npx --yes @opengsd/gsd-core@1.7.0 --codex --global
Then install shipyard from a checkout (the generator and the deterministic scripts
come from the repo, so this path needs the clone — there is no marketplace for
Codex):
`bash`
git clone https://github.com/serhii-nochevnyi/shipyard && cd shipyard
make install-shipyard-codex # or: bash scripts/install-shipyard-codex.sh
This generates Codex skills from the Claude commands (via gsd-core's own$shipyard-route
converter — , $shipyard-investigate, $shipyard-decompose,$shipyard-deliver, $shipyard-bench), registers the delivery subagents in$CODEX_HOME/config.toml (non-destructively), copies the deterministic$CODEX_HOME/shipyard/
scripts/references/workflows under , and installs thedeliver
runtime-agnostic GSD capability that contributes the blocking Gate 2 (ticket
graph) and UAT gates — the same gates the Claude runtime uses.
Because Codex has no Workflow tool, runs its built-in agent path:spawn_agent
deterministic bookkeeping in Node scripts, agentic work via Codex .
<!-- keep in sync with commands/decompose.md -->agent_skills
One config detail matters on Codex. The delivery-rules contract reaches GSD's
planner and executor through in .planning/config.json, and theruntime
working value depends on : the plugin-namespaced formglobal:shipyard:delivery-rules is resolved only on the claude runtime andglobal:shipyard-delivery-rules
is silently skipped elsewhere. On Codex use the bare form, which resolves from ~/.agents/skills — exactlystate-sync
where this installer puts it. warns when the form cannot resolve on$CODEX_HOME/AGENTS.md
your runtime.
It also writes a managed "shipyard auto-route" block into, so a defined scope of work is routed through shipyard$shipyard-*
(research-first, proportionate GSD) without the user invoking by
hand.
Auto-route on host Claude Code
Both runtimes get the auto-route nudge, by different mechanisms: Codex through the$CODEX_HOME/AGENTS.md
managed block in that its installer writes (above), ClaudeUserPromptSubmit
Code through a hook. Inside the container the hook is already
installed by the overlay build, so a scope of work is routed through shipyard
without you invoking anything.
To get the same on your host Claude Code (it edits your user settings,
not the plugin):
`bash`
make install-shipyard-claude-hook # or: make remove-shipyard-claude-hook
It writes ~/.claude/hooks/shipyard-auto-route.sh and merges a hook into~/.claude/settings.json (idempotent, preserving your other hooks). On a running/hooks
session, open once or restart to load it.
Installing the conveyor into host Claude Code
From GitHub, no clone — the repo is itself a plugin marketplace:
`bash`
claude plugin marketplace add serhii-nochevnyi/shipyard
claude plugin install shipyard@shipyard # restart Claude to apply
That gives you the five commands and the delivery-rules skill. The blocking Gate 2
/ UAT gates ship as a GSD capability, which needs the checkout (its installer
stages the validator with its sibling modules), as does the Codex install:
`bash`
git clone https://github.com/serhii-nochevnyi/shipyard && cd shipyard
make install-shipyard-capability # Gate 2 + UAT gates, global scope
make install-shipyard-claude-hook # optional: auto-route
Developing on the checkout instead? Register it as a directory marketplace and
refresh from disk:
`bash`
claude plugin marketplace update delivery-pipeline # refresh from this checkout
claude plugin update shipyard@delivery-pipeline # restart Claude to apply
make install-shipyard-capability # Gate 2 + UAT gates, global scope
The capability installer stages the validator with its sibling modules; agsd-tools capability install pointed straight at capabilities/ would leave themake install-shipyard-codex
gate unable to load its parser. does the equivalent
for Codex as part of its own run.
The plan:post gate is installed at global scope but is applicability-scoped: itdelivery:
stays inert in projects that carry no blocks, and fails closed for.planning/config.json
real conveyor projects. Opt a project out entirely with → pipeline.graph_gate: false.
Set SHIPYARD_CODEX_PHASE=1 to install investigate+decompose only and leavedeliver out. Skills land in ~/.agents/skills; nothing outside shipyard's own
files is modified.
Prerequisites
Per path — only the container path needs Docker at all:
- Container (Claude Code): Docker with Compose support; a Claude Pro or Maxkubectl
subscription; for the Kubernetes deployment and make test-k8s.claude
- Host Claude Code: the CLI and gsd-core installed for it.codex
- Host Codex CLI: the CLI with its own access (ChatGPT plan or APInpx --yes @opengsd/gsd-core@1.7.0 --codex --global
key) and gsd-core installed for Codex
().gh
- Any path that opens PRs: authenticated, plus a valid GITHUB_TOKEN orGH_TOKEN
for git/gh access inside the container (optional there).
- a local SSH agent if you need private Git access at runtime (recommended over
exposing on-disk keys — see "SSH access" below)
The remaining sections up to [Plugins](#plugins) describe the container; the
conveyor itself needs none of it.
Local build
1. Copy .env.example to .env.CLAUDE_CODE_OAUTH_TOKEN
2. Generate a on the host by running claude setup-token, then set it in .env.
3. Build the base image:
`bash`
make build-base GIT_USER_NAME="Your Name" GIT_USER_EMAIL=you@example.com
GIT_USER_NAME and GIT_USER_EMAIL are required and have no default — they/home/dev/.gitconfig
become inside the image, so they author every commit made
in the container. To reuse your host identity:
`bash`
make build-base \
GIT_USER_NAME="$(git config --global user.name)" \
GIT_USER_EMAIL="$(git config --global user.email)"
(The Makefile does not read .env; export the two variables or pass them permake build-base
invocation. and a bare docker build both fail with the
explicit reason when either is empty.)
make build-base also stages safe SSH client files from your local ~/.ssh into the build context. It copies only config, known_hosts, and known_hosts2, and it skips private keys. A host with no ~/.ssh is fine — the staging directory is created empty and the container relies on agent forwarding.
4. Build the overlay image:
`bash`
make build-dev-image
The overlay image installs the following during build:
- gsd-core — the Claude Code delivery plugin, installed from npm vianpx --yes @opengsd/gsd-core@<version> --claude --global --profile=full
.~/.claude
This writes Claude Code plugin configuration under inside the image.2c606141936f1eeef17fa3043a72095b4765b9c2
gsd-core is NOT installed from the Claude Code marketplace; it is installed via npx.
- andrej-karpathy-skills — a Claude Code plugin staged from a pinned Git ref
() and registered with claude plugin.delivery-pipeline
- shipyard (from the in-repo marketplace) — a Claude Codeplugins/delivery-pipeline/
plugin () implementing the multilevel deliverydocs/gsd_multilevel_delivery_pipeline.md
pipeline from : /shipyard:route/shipyard:investigate
(entry router), (deep investigation → ADR),/shipyard:decompose
(ADR → ticket DAG), /shipyard:deliver (per-ticket/shipyard:bench
worktree → PR babysat to green with CodeRabbit/Copilot reviewer
re-initialization), and (off-conveyor direct work).claude-plugins-official
- skill-creator, code-simplifier, github (GitHub MCP server), and
typescript-lsp — installed from the official marketplaceanthropics/claude-plugins-official
(). Plugin versions are pinned by the marketplace'stypescript-lsp
GitHub ref at clone time. The plugin wires typescript-language-server.ts
(already in the base image) into Claude Code, covering , .tsx, .js, and .jsx.UserPromptSubmit
- the shipyard auto-route hook, into the image's own~/.claude/settings.json
.
The base image bakes in:
- Claude Code CLI (@anthropic-ai/claude-code, pinned version) installed via npm.GIT_USER_NAME
- Git identity: whatever you passed as / GIT_USER_EMAIL (required build args, no default)init.defaultBranch=main
- Git defaults: , push.autoSetupRemote=true, color.ui=auto,fetch.prune=true
, pull.rebase=false, pull.ff=onlycontext7-mcp
- safe SSH client files from your local profile when present
- binary (baked in, no runtime npx -y needed)typescript-language-server
- and typescript for LSP support
Private keys are not baked into the image, and by default they are not mounted
into the running container either.
Authentication
Claude Code requires a valid OAuth token. Generate one on your host machine before starting the container:
`bash`
claude setup-token
Copy the token into .env as CLAUDE_CODE_OAUTH_TOKEN=<token>. The container reads this variable at startup.
Run with Docker
`bash`
make run-docker
The container starts in /workspace, which is bind-mounted from WORKSPACE_DIR./workspace
(default ). The repo checkout itself is deliberately not mounted.env
— it holds with your OAuth token, and the session runs withbypassPermissions. Point WORKSPACE_DIR at whatever you want visible instead.
SSH access
Authentication goes through the forwarded SSH agent by default: Compose binds/run/host-services/ssh-auth.sock
the agent socket to inside the container andSSH_AUTH_SOCK
sets to that path. On macOS, Docker Desktop proxies the hostSSH_AUTH_SOCK_HOST=$SSH_AUTH_SOCK
agent at that magic path automatically — passphrase-protected keys and
certificate-based setups (e.g. Teleport) work without copying anything into the
container. On a Linux host, point the bind at your real agent socket by setting in .env. Verify from inside the containerssh-add -l
with .
Your SSH client config is mounted read-only at /home/dev/.ssh-host and/home/dev/.ssh
copied by the entrypoint into a writable (writable so ssh canconfig
record a new host key; existing files are never overwritten). The mount source
defaults to the build-staged safe subset (, known_hosts) — so your
private keys stay on the host.
If you genuinely cannot use agent forwarding, set SSH_DIR=${HOME}/.ssh in.env to mount your full ~/.ssh read-only instead. That exposes your private
keys to the container; prefer the agent.
The host ~/.config/gh directory is mounted read-only at /home/dev/.config/gh, so the GitHub CLI (gh) can use your existing host authentication inside the container. make up and make run-docker create ~/.config/gh on the host if it does not exist (preventing Docker from creating a root-owned directory in its place). The GITHUB_TOKEN / GH_TOKEN environment variables are also forwarded into the container for token-based access.
MCP Servers
The image preconfigures two MCP servers by default:
- Atlassian Rovo — via HTTP (https://mcp.atlassian.com/v1/mcp)context7-mcp
- Context7 — via stdio using the baked binary from@upstash/context7-mcp
. Context7 does not rely on npx -y or runtime npm
downloads.
At container startup, the entrypoint merges these MCP server configurations~/.claude.json
non-destructively into — adding missing entries without
overwriting user customizations.
Atlassian OAuth
Because macOS stores OAuth tokens in the Keychain (not a portable file), Atlassian
Rovo authentication must run inside the container. The documented flow is:
1. Copy .env.example to .env and set CLAUDE_CODE_OAUTH_TOKEN.make up
2. Start a persistent container: make bootstrap-atlassian-oauth
3. Run the bootstrap:
make up creates the host directories the container mounts (including the state
directory) before starting, preventing Docker from creating root-owned paths in
their place.
make bootstrap-atlassian-oauth runs scripts/bootstrap-atlassian-rovo-oauth.shdocker exec -it
on the host. That script s into the running dev container and
executes:
``
claude mcp login atlassian-rovo --no-browser
A remote MCP server authenticates over an OAuth loopback callback that the hostclaude -p
browser cannot reach inside a container, so a headless flow can neverclaude mcp login --no-browser
receive the authorization code. (Claude Code
>= 2.1.191) instead prints the authorization URL and waits. The flow is:
1. Open the printed URL in your host browser and approve access.http://localhost:<port>/callback
2. The browser tries to redirect to and shows a
connection error — this is expected (the callback server is inside the container).
3. Copy the full redirect URL from the address bar and paste it back at the prompt.
Credentials land in /home/dev/.claude/.credentials.json and are mirrored to thedocker compose exec dev claude mcp logout atlassian-rovo
persisted state directory (see below), so they survive container recreation.
(If a half-finished attempt blocks a retry, run first.)
Persistence
One host directory is bind-mounted for state that must outlive the container:
- CLAUDE_STATE_DIR (default ./.claude-state) maps to/home/dev/.claude-state
. The entrypoint restorescredentials.json
from it into ~/.claude/.credentials.json at start and
mirrors the live file back whenever it changes.
It is a directory rather than a single-file mount on purpose: a bind-mountedrename(2)
file cannot be replaced by , so any writer that saves atomicallyCLAUDE_CREDENTIALS_FILE=./.claude-credentials.json
would fail on it outright. An older layout used; make up migrates that
file into the state directory automatically on first run.
Everything else under ~/.claude is baked into the image or generated at startup
and is ephemeral — it is not persisted across container recreation.
The host's full ~/.claude is never mounted.
.cache-home (mounted at ~/.cache) accumulates per-session MCP server logs;make clean-cache prunes the ones older than a week.
LSP Support
TypeScript/JavaScript LSP support is provided by typescript-language-servertypescript-lsp
(shipped in the base image) and wired into Claude Code via the official claude-plugins-official
plugin from . This covers .ts, .tsx, .js, and .jsx files.
The plugin is installed from the official marketplace during the overlay build and loaded
when Claude Code starts an interactive session.
Plugins
Baked into the container image (hence all Claude Code plugins — on Codex themake install-shipyard-codex
equivalents are installed as skills/subagents by ):
- gsd-core (@opengsd/gsd-core) — Claude Code delivery plugin with full profile, installed via npx.claude plugin
- andrej-karpathy-skills — staged from pinned commit, registered via .plugins/delivery-pipeline/
- shipyard — in-repo (); investigation → ticket DAG →/shipyard:route
per-ticket worktree/PR delivery. Commands: ,/shipyard:investigate
, /shipyard:decompose, /shipyard:deliver,/shipyard:bench
.claude-plugins-official
- skill-creator — from ; helps create new Claude Code skills.claude-plugins-official
- code-simplifier — from ; reviews and simplifies code.claude-plugins-official
- github — from ; the official GitHub MCP server plugin.claude-plugins-official
- typescript-lsp — from ; TypeScript/JS LSP via typescript-language-server.
Deploy to Kubernetes
The manifests in k8s/ run the same overlay image as a single-replicakubectl exec
StatefulSet you attach to with . One PVC backs /workspace,~/.cache and the credential state directory via subPath.
1. Push the built overlay image to a registry reachable by the cluster and updateimage:
the field in k8s/statefulset.yaml.k8s/secret.example.yaml
2. Create the real secret — is a template and ismake deploy-k8s
deliberately not applied by :
`bash`
kubectl create secret generic claude-shipyard-secrets \
--from-literal=CLAUDE_CODE_OAUTH_TOKEN="$(claude setup-token)" \
--from-literal=GITHUB_TOKEN="$GITHUB_TOKEN" \
--from-literal=GH_TOKEN="$GITHUB_TOKEN"
3. Apply manifests:
`bash`
make deploy-k8s
4. Attach:
`bash`
kubectl exec -it claude-shipyard-0 -- bash -lc 'cd /workspace && claude --dangerously-skip-permissions'
Smoke tests
make test-fast needs neither Docker nor the network — run it on every edit:
`bash`
make test-fast # unit + graph + worktree + docs + ssh-sync
make test-unit # frontmatter parser, model policy, ticket↔PR matching
make test-graph # Gate 2 contract + plan:post gate applicability, on fixtures
make test-worktree # epic-branch.sh + ticket-worktree.sh against real git repos
make test-docs # README + .env.example invariants
make test-ssh-sync # sync-local-ssh-config.sh behaviour
The rest build images or reach the network:
`bash``
make test-k8s # kubectl dry-run over k8s/
make test-base
make test-overlay
make test-runtime
make test-mcp-runtime
make test-codex-shipyard # generator + installer produce valid Codex artifacts (needs network)
make test # everything, in order
This is the containerized Claude Code path. For a host install (Claude Code or
Codex CLI) skip to [Shipyard on the OpenAI Codex CLI](#shipyard-on-the-openai-codex-cli)
or [Installing the conveyor into host Claude Code](#installing-the-conveyor-into-host-claude-code).
``bash`
make dev
make dev runs the guided launcher (scripts/dev.sh): it creates .env if/workspace
missing, builds the images if needed, starts the container, and then asks in turn
whether to log in to Claude, authenticate MCP servers (e.g. Atlassian Rovo), and
which project to work in — clone a new repo into or pick an existingclaude
one there. It finally attaches a or bash session **inside that project
directory**. Each step is skipped if already done, so it is safe to re-run.
Inside this isolated container, Claude always runs without per-action permissionpermissions.defaultMode: "bypassPermissions"
prompts: the image bakes into~/.claude/settings.json, and the entrypoint pre-seeds the one-time bypass/workspace
acceptance plus per-directory trust for and every project inside itshipyard-trust
(the helper covers repos cloned later). So a bare claude fromclaude --dangerously-skip-permissions
any shell behaves the same as (whichmake claude and the launcher's "claude" option still pass explicitly). Use this
only in the throwaway container, never against your host shell.
Individual targets if you prefer to drive it yourself:
| Command | Does |make up
|---|---|
| | start the persistent container (docker compose up -d; builds the image first if missing) |make claude [DIR=subdir]
| | attach a claude --dangerously-skip-permissions session (in /workspace/subdir if DIR is given) |make shell [DIR=subdir]
| | attach a bash shell (in /workspace/subdir if DIR is given) |make clone REPO=<git-url>
| | clone a repo into /workspace (set WORKSPACE_SUBDIR=name to rename) |make bootstrap-atlassian-oauth
| | authenticate the Atlassian Rovo MCP server |make run-docker
| | one-off ephemeral session (docker compose run --rm) |make clean-cache
| | prune MCP server logs older than 7 days from .cache-home` |
Per path — only the container path needs Docker at all:
- Container (Claude Code): Docker with Compose support; a Claude Pro or Max
subscription; kubectl for the Kubernetes deployment and make test-k8s.
- Host Claude Code: the claude CLI and gsd-core installed for it.
- Host Codex CLI: the codex CLI with its own access (ChatGPT plan or API
key) and gsd-core installed for Codex
(npx --yes @opengsd/gsd-core@1.7.0 --codex --global).
- Any path that opens PRs: gh authenticated, plus a valid GITHUB_TOKEN or
GH_TOKEN for git/gh access inside the container (optional there).
- a local SSH agent if you need private Git access at runtime (recommended over
exposing on-disk keys — see "SSH access" below)
The remaining sections up to [Plugins](#plugins) describe the container; the
conveyor itself needs none of it.