ShipifyAI + Claude

Claude Configuration

Ideal Claude configuration for the ShipifyAI worker

The ShipifyAI worker runs Claude Code in non-interactive print mode and depends on Claude not stopping to ask for permission. Every blocked command becomes a halted automation, a Slack notification, and human time spent on approval. This article describes the configuration that maximises worker throughput without opening dangerous doors. The recommended setup leans on system-level isolation rather than on Claude-level restrictions.

Why a dedicated, isolated machine

Claude executes the code it writes. If you give it access to your own laptop, it can see SSH keys, AWS tokens, browser sessions, and credentials for Hygraph, Stripe, OpenAI, SendGrid, and any other service whose secrets you have ever stored locally. Even without bad intent, a single debugging line such as printing the contents of `~/.aws/credentials` lands in a log or a context window and is no longer fully under your control.

The right model is the opposite. The worker machine knows only what ShipifyAI gives it for one project, namely a GitHub PAT scoped to a single repository, a Jira token, a Slack token, and nothing else. The machine should be a container, an LXC instance, or a dedicated VM. It should have no `~/.ssh`, no `~/.aws`, no `~/.config/gcloud`, no `.env` files from other projects. It should ship only the runtimes the worker needs, namely Node.js, Python, Claude Code, the ShipifyAI CLI, and the project-specific toolchain. Egress should be limited to the small list of domains the worker actually needs, and the machine should have no network path to your production databases or application secrets.

The recommended path: allowAll set to true

In the official Docker image the same effect is achieved differently. The container's entrypoint invokes Claude Code with the `--dangerously-skip-permissions` flag, which bypasses every permission check for the lifetime of the process and overrides any settings file — including a strict allowList that the project repository may ship in its own `.claude/settings.json`. The flag and the `allowAll` setting are equivalent in behaviour; the flag is preferred inside the container because it is fixed at image-build time and cannot be weakened by configuration that the worker happens to clone. The Dockerised worker article covers this in detail.

This is safe because the security boundary is moved one level down. A destructive command such as `rm -rf` only deletes the working clone of the repository, which the worker can re-clone from scratch. A `git push` to the protected default branch is rejected by GitHub branch protection rules. Secrets cannot leak from the machine because the machine does not contain them. Outbound network attacks are constrained by the egress firewall. Persistence is mitigated because the worker container is restarted on a regular schedule. A force push to an upstream is impossible because the GitHub PAT was generated without the Administration scope and the protected branch rejects forced updates.

The cases where `allowAll` is the wrong choice are easy to recognise. If you run the worker on your own developer laptop, do not enable it. If the machine has any path to production, such as a kubeconfig pointing at a production cluster or AWS credentials that work against the live account, do not enable it. If the machine is shared with other processes or other users, do not enable it. In any of those cases the conservative path described next is the right answer instead.

The conservative path: a custom allowList

If for any reason you cannot give Claude `allowAll`, the alternative is to write an explicit allowList in the repository's `.claude/settings.json`. The allowList enumerates every shell command Claude is allowed to invoke, plus reads, edits, and writes scoped to the repository. A typical allowList covers the standard Git operations such as status, diff, add, commit, checkout, fetch, pull, log, and pushes restricted to feature branches. It covers the package manager operations the project actually needs, such as `npm install`, `npm run build`, `npm run test`, `npm run lint`, and `npm run type-check`. It covers test runners such as Jest or Vitest, and the TypeScript compiler. Reads, edits, and writes are scoped to the project working directory and not allowed to escape it.

A minimal example looks like this:

```

{

 "permissions": {

   "allow": [

     "Bash(git status)",

     "Bash(git diff:*)",

     "Bash(git add:*)",

     "Bash(git commit:*)",

     "Bash(git push origin feature/*:*)",

     "Bash(git checkout:*)",

     "Bash(git fetch:*)",

     "Bash(git pull:*)",

     "Bash(git log:*)",

     "Bash(npm install)",

     "Bash(npm run build)",

     "Bash(npm run test)",

     "Bash(npm run test:*)",

     "Bash(npm run lint)",

     "Bash(npm run type-check)",

     "Bash(jest:*)",

     "Bash(vitest:*)",

     "Read(./**)",

     "Edit(./**)",

     "Write(./**)"

   ],

   "deny": [

     "Bash(git push origin main:*)",

     "Bash(git push --force:*)",

     "Bash(rm -rf:*)",

     "Bash(curl:*)",

     "Bash(wget:*)",

     "Bash(sudo:*)",

     "Read(/etc/**)",

     "Read(~/.ssh/**)",

     "Read(~/.aws/**)"

   ]

 }

}

```

When Claude tries to execute a command that is not on the allowList, the worker enters a structured pause. The CLI detects that Claude is waiting for approval and calls POST /ai/report-permission-blocked on the backend with the command name and the task identifier. The backend posts a Slack notification to the project channel saying that command approval is required, naming the command and the task, and asking the user to approve in the terminal or to extend the allowList. The CLI then waits, blocking the task. Once the user approves, the CLI resumes from where Claude stopped. If no approval arrives within the configured timeout, which defaults to one hour, the CLI gives up on the task and reports a timeout to the backend.

The trade-off is straightforward. A conservative allowList means less autonomy, more Slack notifications, and more chance of a task stalling overnight. An allowAll configuration on an isolated machine means full autonomy and no notifications. Pick the model that matches the level of trust you have in the system layer.

CLAUDE.md, the instruction file for the AI

The most important file in the repository, from the AI's perspective, is `CLAUDE.md`. Claude reads it automatically every time it starts in the project directory, and it shapes the quality of every generated change. The file should describe the project at a high level, explain the repository structure, list runtime requirements, document the development commands, capture coding conventions, capture Git conventions, describe the testing approach, and end with an explicit checks section that names what Claude must do before considering a task complete.

A good `CLAUDE.md` is short. Anything that can be expressed as a skill should be moved to the `.claude/skills` directory rather than bloating the main file. The checks section is the most load-bearing part. It should require Claude to run the linter, the type checker, the test suite, and to update relevant documentation before reporting a task as done.

A minimal but functional `CLAUDE.md` body covers project overview, repository structure, requirements, development commands, code convention, Git convention, and a checks section. The conventions section is where you encode preferences such as kebab-case file names, a blank line before every return, no `as any` unless explicitly justified, English-only code and documentation, and the maximum line length.

Skills, how to write them well

A skill is a focused mini-instruction that Claude loads on demand when a task triggers it. Skills keep `CLAUDE.md` short and let you give Claude specific, actionable guidance at the moment it is needed. Skills live in `.claude/skills` as Markdown files, each one with a frontmatter block declaring its name and description.

The `description` field is the single most important part of a skill. Claude picks which skill to load based on this field, so it must clearly say when to use the skill and what it covers. Start with a verb, mention the trigger, and be specific. The body of the skill should be in imperative voice, structured with short sections and bullet lists rather than long prose. Always end with a definition-of-done section so Claude knows when to stop.

A good `run-tests` skill describes when to use it, namely before opening a pull request and after any code change in the relevant directories. It describes how to run tests for backend and frontend separately. It describes what to do on failure, telling Claude to fix the root cause and never to skip or use `.only`. It ends with a definition of done that requires all tests to pass, coverage to be at or above the existing baseline, and any new behaviour to have at least one test.

A good `self-review` skill is loaded immediately after finishing implementation, before the commit. It instructs Claude to read every changed line as if reviewing it for someone else, to look for security issues such as SQL injection, XSS, or leaked secrets, to look for dead code, to check naming, and to confirm that the change does only what the task required without bonus refactors. It contains a check list covering forgotten `console.log` calls, unjustified `any` types, commented-out code, hardcoded secrets, missing tests, and required passes of lint and type-check. The closing rule is that any issue found must be fixed before commit, never deferred.

A good `add-test` skill explains where new tests go, namely colocated next to the source file with a matching name. It names the testing stack, namely Jest with Supertest and a real PostgreSQL via testcontainers on the backend, and Vitest with React Testing Library on the frontend. It requires a happy-path case, an edge case, and a failure case. It enforces naming conventions and a definition of done that includes a passing test, sufficient coverage, and no leftover `it.only` or `describe.only` markers.

Other skills that pay for themselves quickly include `migration` for generating and running MikroORM migrations safely, and `dont-touch` for naming areas of the repository that the AI must never modify. The pattern in all of them is the same. The `description` says when. The body says how. The closing section says how to recognise success.

MCP servers, with caveats

MCP servers extend Claude with structured access to tools beyond the shell. They can expose a typed interface to Jira, to a database, to documentation systems, or to memory. Two warnings apply before you reach for them.

First, MCP servers that connect Claude directly to your Jira or your GitHub bypass the ShipifyAI backend. The security model in the next article rests on the fact that the AI does not directly modify your Jira, the backend does, with a strict whitelist of allowed operations. Plugging an MCP server into the worker undoes that guarantee. Do not connect Jira or GitHub MCP servers on the worker machine.

Second, even safer MCP servers expand the attack surface. The filesystem MCP, scoped to the project working directory, is generally fine. A fetch MCP that lets Claude pull arbitrary URLs is fine only if you restrict the allowed domains. A memory MCP is experimental and worth treating with suspicion. The general rule is to add MCP servers when they buy you something concrete and to leave them out when in doubt.

What to commit to your repository

The full Claude configuration package for a project lives in two places. At the root, `CLAUDE.md` carries the high-level instructions. Inside `.claude` you keep `settings.json` with either `allowAll` or an explicit allowList plus a small deny list, a `skills` directory with the per-task skill files such as `run-tests.md`, `self-review.md`, `add-test.md`, `address-pr-feedback.md`, `migration.md`, and `dont-touch.md`, and optionally `mcp.json` if you have decided to enable MCP servers. A `.claudeignore` at the root, similar in spirit to `.gitignore`, lets you keep specific paths out of Claude's context.

Common pitfalls to avoid

The most common pitfall is shipping the worker without a `CLAUDE.md`. Without it the AI has to guess your conventions and the generated code looks off-style. Add even a minimal `CLAUDE.md`.

The third pitfall is skills written in descriptive prose instead of in imperative voice. Claude does not load them, or loads them at the wrong time, because the description does not clearly say when to use them. Always start descriptions with a verb and a trigger.

The fourth pitfall is enabling `allowAll` on a developer laptop. Run the worker on a dedicated, isolated machine, not on the host where you keep your other secrets.

The fifth pitfall is wiring MCP servers to Jira or GitHub from the worker. That bypasses the security boundary and gives the AI direct access to systems that should only be touched by the backend.

The last pitfall is leaving the default branch unprotected. Branch protection on the default branch is mandatory, not optional. The security article explains the layered protections in detail.

Where to go next

The control article explains how a human can reclaim a task or a pull request from the AI. The security article explains the layered protections that keep the AI inside its sandbox. The Dockerised worker article describes the official container image, which is the recommended way to put the recommendations in this article into practice without writing a settings file by hand.