The ShipifyAI worker is the machine where the CLI agent runs. It picks up tasks from Jira, generates code through Claude Code, runs the test suite, and opens pull requests. Setting it up is split into two layers: the ShipifyAI dashboard, where you configure the project on our side, and the worker machine, where you install and start the agent on your side. The whole setup takes around fifteen minutes if you have access to all the integrations in advance.
The worker connects to four external roles — code repository, task management, notifications, and Claude — and for each role you choose one provider and generate the corresponding credential upfront. The choice is per-project, so different ShipifyAI projects on the same dashboard can mix and match providers freely.
The worker machine itself must have outgoing internet access to the APIs of the providers you picked (api.github.com and/or your GitLab host, your Atlassian site and/or api.github.com for Projects, slack.com and/or your Teams webhook host), the Anthropic API, and the ShipifyAI backend. If you operate behind an egress firewall, allowlist only the domains matching the providers you selected for the project.
Open the ShipifyAI dashboard, register with your first name, last name, email, password, and company name. Confirm the activation email — at this point your account exists but is not yet active. Every new account is manually reviewed and approved by the ShipifyAI team, which usually takes up to 72 hours; we do this to keep the platform free of automated abuse and to make sure the company details you entered match a real organisation. You will receive a second email once the approval lands, and only then can you log in to the dashboard.
After you log in for the first time, before doing anything else, enable two-factor authentication under Profile and 2FA. The CLI token you will generate later gives access to your repositories, so the account itself must be protected with 2FA.
Go to Dashboard, then Projects, then New project. The form asks for the following information:
The minimum wait time exists so that humans have a window to claim a task before the AI does. Setting it to one or two days is a common pattern when you want the AI to handle backlog only.
The choice of provider is per-project. You can have one project on Jira and another on GitHub Projects in the same dashboard, and the worker handles each one transparently — the CLI never sees provider-specific shape, it receives a normalised task. The same independence applies to the code repository choice: one project can host code on GitHub while another lives on GitLab. Pick each provider to match where your team already tracks work and stores code. The integration sections below cover GitHub, GitLab, Jira, and GitHub Projects credentials separately; only the two you selected for this project need to be configured.
You connect GitHub by pasting a Personal Access Token into the project form. ShipifyAI requires a **fine-grained** token, because it lets you scope access to a single repository and to a small set of permissions. Classic tokens are not supported — they cannot be scoped to one repository, so the worker would technically have access to every repository the user can read. The procedure below uses the fine-grained variant.
Open github.com in your browser and make sure you are logged in to the account that owns the repository, or that has write access to it through an organisation. Click your avatar in the top right corner and choose Settings from the dropdown. In the left sidebar of the settings page scroll all the way down and click Developer settings, which is the last item. On the developer settings page click Personal access tokens in the sidebar, then click Fine-grained tokens. Click the green Generate new token button in the top right corner.
Fill in the form as follows:
Scroll to the Repository permissions section and set exactly the following — leave every other permission at No access:
`Pull requests: `Read and write — lets the worker open pull requests and post review replies.
Leave the Account permissions section entirely at No access. The worker does not need anything outside the repository.
Click Generate token at the bottom. GitHub shows the token once, on the next screen, prefixed with `github_pat_`. Click the copy button. You will not be able to see this value again. Paste it directly into the GitHub Connection field of the ShipifyAI project form. The backend validates the token by calling GET /user on the GitHub API and stores it AES-256 encrypted in the `github_connections` table. If validation fails the form rejects the token with an error explaining what went wrong, typically an expired token, a typo, or insufficient permissions.
If you chose GitHub Projects as the task management provider, you also need a second GitHub Personal Access Token, generated through exactly the same fine-grained flow described above, but scoped differently. ShipifyAI deliberately keeps the code-side token and the task-management-side token as two separate credentials, mirroring the Jira pattern of treating task-board access as its own integration. In principle one PAT could carry both sets of permissions, but the platform asks for them separately so you can rotate, audit, and revoke them independently.
Generate the second token the same way: avatar, Settings, Developer settings, Personal access tokens, Fine-grained tokens, Generate new token. Name it differently from the code-side token, for example "ShipifyAI Projects for ProjectA", so you can tell the two apart in the GitHub UI. Repository access can be Public Repositories (read-only) since this token does not interact with repository contents directly. The permissions are where this token diverges from the code-side one: under Organization permissions (or Account permissions if the project is user-owned) set Projects to Read and write. Under Repository permissions set Issues to Read and write on the same repositories whose issues will appear in the project. Leave Contents, Pull requests, Administration, and every other permission at No access. Generate the token, copy the `github_pat_` value, and paste it into the GitHub Projects Connection field of the project form, along with the project owner login (the org or user that owns the GitHub Project) and the owner type (Organization or User). The backend validates the token by calling `GET /user` and then `GET /{orgs|users}/{owner}/projectsV2` with the header `X-GitHub-Api-Version: 2026-03-10`, and stores it AES-256 encrypted in the `github_projects_connections` table.
The project form will then ask for the GitHub Project number (visible in the project URL, for example `5` from `https://github.com/orgs/acme/projects/5`) and for the Status field id plus the four option ids for source / in-progress / in-review / incomplete. You can discover these by running `gh api /orgs/<owner>/projectsV2/<n>/fields -H "X-GitHub-Api-Version: 2026-03-10"` against your project, or by hitting the same endpoint with curl using the token you just generated. Paste the ids into the form so the worker can move items between columns via a single `PATCH /orgs/<owner>/projectsV2/<n>/items/<item_id>` call per transition, without runtime field-resolution lookups.
This section applies only if you chose GitLab as the project's code repository provider. If you chose GitHub, skip ahead to Connect Jira (or Connect Slack if you also chose GitHub Projects for task management).
You connect GitLab by providing a Personal Access Token and a host URL. The default host is `https://gitlab.com`; for self-hosted GitLab supply the full base URL of your instance. The PAT is what you generate now.
Open your GitLab account, click your avatar in the top right, and choose **Edit profile**. In the left sidebar click **Access tokens** (the URL is `/-/user_settings/personal_access_tokens` on any GitLab host). Click **Add new token**. If GitLab offers a choice between a fine-grained token (beta) and a standard token, pick the **standard** one — the one with a **Scopes** checkbox list (including `api`). ShipifyAI does not support fine-grained tokens yet: they are resource-scoped and cannot be validated against `GET /api/v4/user`, and their beta permission model does not reliably cover the merge-request and review endpoints the worker uses.. Fill in the form as follows:
Click **Create personal access token**. GitLab shows the token once, on the next screen, prefixed with `glpat-`. Click the copy button. You will not be able to see this value again. Paste it directly into the GitLab Token field of the ShipifyAI project form, alongside the host URL. The backend validates the token by calling `GET /api/v4/user` with the `PRIVATE-TOKEN` header and stores it AES-256 encrypted in the `gitlab_connections` table. If validation fails the form rejects the token with an error explaining what went wrong, typically an expired token, a typo, a wrong host URL, or insufficient scopes.
The worker uses the token in two distinct ways. For git operations the clone URL is rewritten to `https://oauth2:<token>@<host>/<path>.git`, which is GitLab's standard pattern for HTTPS-token authentication. For REST operations against the GitLab v4 API the same token travels in the `PRIVATE-TOKEN` header. Both forms are derived from the single stored credential — there is no second token to manage.
If you host code on GitLab but track tasks in Jira or GitHub Projects, the GitLab token plays the code-repository role only. The AI still picks up tasks from your Jira board or GitHub Project, but every clone, push, and merge request happens on GitLab.
This section applies only if you chose Jira as the project's task management provider. If you chose GitHub Projects, you already configured the corresponding credentials in the previous section and can skip ahead to Connect Slack.
You connect Jira by providing three pieces of information: your Atlassian email address, an API token, and the URL of your Jira site. The token is what you generate now, the email is the one you use to log in to Atlassian, and the site URL is the address bar value when you have your Jira board open.
Open id.atlassian.com in your browser. If you are already logged in to Atlassian you go straight to your account profile. If not, log in with the same credentials you use for Jira itself. In the left sidebar click Security. On the security page scroll to the section called API tokens, and click the link Create and manage API tokens. This opens id.atlassian.com/manage-profile/security/api-tokens directly.
Click the Create API token button. A dialog appears asking for a label. Use a name that identifies where the token will be used, such as "ShipifyAI worker for ProjectA". Click Create. The next screen shows the token value. Click the Copy button. The dialog warns you that the token will not be shown again, which is true. If you close the dialog without copying the value you must delete it and generate a fresh one.
Paste the token into the project form in the Jira API token field. In the Jira email field type the email address you use to log in to Atlassian, in lowercase, exactly as it appears in your Atlassian profile. In the Jira site URL field type the URL of your Jira workspace, in the form `https://your-company.atlassian.net`, without a trailing slash and without any path component. You can find this URL by opening Jira in another tab and copying the part of the address before the first slash that follows the domain.
When you submit the form the backend authenticates with Basic Auth using your email and the token, and calls GET /rest/api/3/myself on the Jira API to confirm that the credentials work. It also derives the Cloud ID of your Atlassian instance from the site URL.
The Jira board you want to connect should expose four columns that the worker will use as source, in-progress, in-review, and incomplete. The default Jira workflows in most projects already include "To Do", "In Progress", and "Done", and you typically need to add a fourth column for tasks the AI returned because the description was unclear. To add a column open the board in Jira, click the three-dot menu on the board, then Configure board, then Columns. Click Add column, name it for example "Incomplete", and map it to a Jira status of the same name. Save. Back in the ShipifyAI project form, type the Jira status name that plays each role — source, in-progress, in-review, and incomplete. These names **must match the statuses in your Jira workflow**, because the worker moves an issue by matching the configured name against the workflow's available transitions (it matches on the transition's target status). Matching is case-insensitive and trims surrounding spaces, but it does not translate: if your Jira instance runs in a non-English language, enter the localized status names exactly as they appear on the board (for example the Polish `W toku` rather than `In Progress`). One subtlety is that the source column is also used to search for new tasks via JQL, where Jira accepts the canonical English status name even on a localized instance; the other three are used only when transitioning an issue, where Jira reports the localized status name. If a transition target does not match any status, the worker logs `No matching Jira transition found` and leaves the issue where it is — the task then stays in the source column and gets picked again, so getting these names right also prevents a re-processing loop.
You connect Slack by providing a Bot Token, which starts with `xoxb-`, and the name of the channel where the bot will post notifications. The token is generated through the Slack app management UI. The procedure has more steps than the previous integrations because Slack requires you to create a small Slack app, declare its scopes, install it into the workspace, and only then read the resulting bot token.
Open api.slack.com/apps in your browser and log in with your Slack workspace credentials. Click the green Create New App button. A dialog asks how you want to create the app. Choose From scratch. Enter "ShipifyAI" as the App Name and select the Slack workspace where the project channel lives from the dropdown. Click Create App. You land on the Basic Information page of the new app.
In the left sidebar click OAuth and Permissions. Scroll down to the Scopes section. The bot token needs the following scopes, all under Bot Token Scopes. Click Add an OAuth Scope and pick `chat:write`, which lets the bot post messages. Add `channels:read`, which lets the backend list public channels and resolve the configured channel name to its internal channel id. Add `groups:read` so the same name-to-id resolution works for private channels. Add `channels:join`, which lets the bot add itself to a public channel before posting. Add `reactions:write`, which lets the bot place a thumbs-up reaction on review comments to acknowledge them. You do not need any User Token Scopes, leave that section empty.
The `channels:read` scope (and `groups:read` for private channels) is not optional: the backend resolves the channel **name** you enter into a channel **id** by calling `conversations.list`. Without the scope that call fails with `missing_scope`, the channel cannot be found, and the notification is silently skipped — even though the token itself is otherwise valid. If you add scopes after the first install, you must click **Reinstall to Workspace** for them to take effect; the bot token changes on reinstall, so copy the new value and update the project's Slack connection.
Scroll back to the top of the OAuth and Permissions page. Click the Install to Workspace button. Slack asks for confirmation that you want to authorise the app to act on the listed scopes. Click Allow. After a moment Slack returns to the same page with a new section at the top called OAuth Tokens for Your Workspace, which contains the Bot User OAuth Token starting with `xoxb-`. Click the Copy button next to it.
Paste the token into the Slack Token field of the ShipifyAI project form. In the Slack channel field enter the channel name in the form `#dev-ai`, including the leading hash. The bot must be a member of any private channel you point it at. Even for public channels it is good practice to invite the bot explicitly, by typing `/invite @ShipifyAI` in that channel, so that you see the bot as a member when reviewing access.
If your workspace uses Enterprise Grid, repeat the workspace selection step on the right workspace, because Bot Tokens are scoped to a single workspace within the grid. If your organisation requires app approval, the Install to Workspace step opens an approval request to a workspace admin instead of installing immediately, and you must wait for that approval before continuing.
This section applies only if you chose Microsoft Teams as the project's notification provider. If you chose Slack, the previous section (Connect Slack) is what you need; skip ahead to Generate a CLI token.
You connect Microsoft Teams differently from Slack. Teams does not use a bot token + channel name pair; it uses a **per-channel webhook URL** generated by the Workflows app inside Teams. That URL is the entire credential — anyone holding it can post to the bound channel — so treat it like any other secret. ShipifyAI stores it securly.
Before you begin, confirm with your Teams admin that the Workflows app is enabled in your tenant. In most organisations it ships enabled by default. If your admin has restricted Workflows you will not be able to generate a webhook URL until they grant access.
Open Microsoft Teams and go to the team and channel where you want the worker to post notifications, for example "ProjectA" team, "#dev-ai" channel. Click the three-dot menu next to the channel name (the "More options" icon that appears on hover) and choose **Workflows**. A dialog opens showing a gallery of workflow templates.
In the search box type **Post to a channel when a webhook request is received**, then select the template card with that exact title. The first screen confirms the workflow name and the Teams account that will own it. Rename the workflow to something descriptive such as "ShipifyAI notifications for ProjectA" — it makes the owner panel easier to navigate later, especially if the channel hosts more than one webhook. Click **Next**. On the next screen confirm the team and channel that will receive the messages (Teams pre-fills the channel you triggered the dialog from). Click **Add workflow**.
Teams now generates the webhook URL on the next screen, prefixed with `https://` and ending with a long opaque token. Copy the URL with the copy button next to it. You will not be able to see this value again from this screen, although you can rotate it later by editing the workflow. Paste the URL directly into the Microsoft Teams Webhook URL field of the ShipifyAI project form. Optionally fill in the **Channel label** field with a human-friendly name like "ProjectA — dev-ai" — this label is for display only and does not affect routing; the URL itself is bound to one specific channel at creation time.
Two operational notes about Workflows worth knowing. First, workflows are owner-scoped rather than channel-scoped; if the team member who created the workflow leaves the organisation the flow can become orphaned. To avoid losing notification access, your Teams admin can add one or more co-owners to the workflow through the Workflows app's owner panel. Second, the deprecated O365 Connector "Incoming Webhook" mechanism is being retired by Microsoft — Workflows is the supported replacement and ShipifyAI assumes you are using the new flow. If you still have an old Connector URL it will work today, but switch to Workflows when convenient.
If you have chosen Microsoft Teams as the notification provider, you do **not** need to configure Slack at all. The form skips the Slack section automatically when the notification provider is set to Microsoft Teams. The reverse is also true: a Slack-configured project does not require a Teams webhook.
In the dashboard go to CLI Tokens and click Generate new token. This is your long-lived master token from which the CLI mints short-lived session tokens valid for twenty-four hours. The first time you use it on a machine, the token is bound to the device. Copy the token immediately. We do not show it again.
The worker should run on a dedicated, isolated machine, ideally a container, a VM, or a dedicated host. We do not recommend to run it on your personal laptop or on a machine that holds your other secrets such as SSH keys, AWS credentials, or Hygraph and Stripe tokens. The reasoning is covered in detail in the security article.
The recommended path is to use the official `shipifyai/worker` Docker image, which ships with every runtime the worker needs already pinned and aligned. The image bundles Python 3.14 for the CLI, Node.js 24 for Claude Code, `git` and `openssh-client` for the repository operations, `npx` and `uvx` for the on-demand launch of MCP servers distributed via npm or PyPI, the `claude` binary itself, and the ShipifyAI CLI as the entrypoint.
The container also reads the environment variable `SHIPIFYAI_DANGEROUSLY_SKIP_PERMISSIONS`, which makes the CLI append `--dangerously-skip-permissions` to every `claude` invocation. Because the container is the isolation boundary — the security boundary sits at the system layer, as described in the security article — the official image ships with this flag **on by default**, so that Claude Code can write files non-interactively without a human to approve each edit. If you run the image on a less-trusted host and want Claude's per-tool permission prompts back, override it explicitly with `-e SHIPIFYAI_DANGEROUSLY_SKIP_PERMISSIONS=false`, and the worker falls back to the allowList behaviour described in the Claude configuration article.
The image also pre-configures a git identity (`ShipifyAI Agent <agent@shipifyai.app>`) so that commits succeed inside the otherwise-fresh container; commit messages are additionally prefixed with `[AI GENERATED]`.
Pull the image and start the worker with:
```
docker pull shipifyai/worker:latest
docker run --rm -it \
--name shipifyai-worker \
--env-file ~/.shipifyai/env \
-v shipifyai-workspace:/workspace \
shipifyai/worker:latest
```
For a quick test without an env file you can pass the token inline instead — the production backend URL is already baked into the image, so the token is the only variable you need:
```
docker run --rm -it \
-e SHIPIFYAI_TOKEN=<your-cli-token> \
-v shipifyai-workspace:/workspace \
shipifyai/worker:latest
```
The env file is the same one described later in this article; at minimum it sets `SHIPIFYAI_TOKEN`, since the production `SHIPIFYAI_API_URL` is baked into the image and `SHIPIFYAI_WORKDIR=/workspace` / `SHIPIFYAI_LOG_LEVEL` are optional. The `SHIPIFYAI_TOKEN` is the only credential you supply to the container. There is no Claude browser login to mount and no second credential file to manage. As noted above, `--dangerously-skip-permissions` is on by default in the image; pass `-e SHIPIFYAI_DANGEROUSLY_SKIP_PERMISSIONS=false` to restore Claude's per-tool permission prompts on a less-trusted host. The named `shipifyai-workspace` volume preserves cloned repositories and their installed dependencies across container restarts; omit it if you prefer a fresh clone on every launch. For a long-running deployment, swap `--rm -it` for a systemd unit, a Docker Compose service, or a Kubernetes Deployment that restarts the container daily.
Create the environment file referenced by `--env-file` above (for example at `~/.shipifyai/env`, with permissions 0600) or pass params directly to docker run. The file should set:
Start the worker with the `docker run` command shown in Part B — the container's default command is `start`, so it boots straight into the loop.
If `shipifyai start` returns 401 Unauthorized, the CLI token has likely been deleted in the dashboard or the device parameters changed. Generate a new token or revoke the device binding and re-bind on first launch.
If Claude reports Permission denied on a shell command, it means the command is outside the allowList. The CLI pauses and the backend posts a Slack notification asking to approve the command. Details on configuring the allowList are in the Claude configuration article.
If the dashboard marks a connection as `requires_reauth`, the external token has been revoked at the source. Generate a new GitHub, Jira, or Slack token and update it in the project form.
If Claude reports tokens exhausted, the CLI waits until the token window resets, which usually takes a few hours, and resumes work using `claude --continue` so that the session context is preserved.
The Claude configuration article explains the recommended `CLAUDE.md`, skills, MCP servers, and allowList settings. The control article explains how to take a task or pull request back from the AI. The security article explains the boundaries the AI cannot cross, and why.