Settings
Shipper resolves persistent settings in one least-to-most-specific order:
built-in defaults -> user-global -> committed repository -> per-repository personal. The
most-specific eligible value wins once. There are no per-setting precedence paths or post-merge
reapplication rules.
Scope eligibility is applied before that single merge. Exactly five settings are repository-only policy:
mirrorModegoverns whether the repository projects local issue state to GitHub.cliVersionrecords the Shipper CLI version expected by the committed configuration.transportselects"origin"or"none".pullRequestsselects"github"or"none".reviewDeliveryis always"local".
These five keys may be set only in committed repository settings because they govern shared repository operation and version policy. A value for one of them in user-global or personal settings is ignored with a warning.
defaultBaseBranch has narrower source eligibility than ordinary preferences: it may be set in
committed repository or per-repository personal settings, but not user-global settings. A
user-global occurrence is ignored with a warning. Within its eligible sources, normal specificity
applies: per-repository personal > committed repository > GitHub auto-detection. A personal
override is machine-local and does not modify the committed settings file.
baseIntegration has the same repository-or-personal eligibility and rejects user-global scope
with a warning. It accepts "rebase" or "merge" and defaults to "rebase". Personal scope is an
escape hatch for a third-party repository whose committed settings cannot be changed, not a
per-developer workflow preference: per-repository personal > committed repository > default.
sharedCache is eligible in user-global, committed repository, and per-repository personal
settings. It defaults to true, with precedence per-repository personal > committed repository > user-global > default. A user-global false disables sharing machine-wide unless a narrower
repository or personal true overrides it.
Choose a scope
Section titled “Choose a scope”Use user-global settings for a preference that should apply to every repository:
shipper settings set commands.default.agent codex --usershipper settings set agentTimeoutMinutes 0 --usershipper settings set reviewDiffCeilingBytes 750000 --userUse committed repository settings for shared team intent. The file lives at
.shipper/settings.json, is created by shipper setup --repo, and should be committed:
shipper settings set mirrorMode mirror-on --reposhipper settings set installCommand 'npm ci' --reposhipper settings set reviewDiffCeilingBytes 750000 --repoUse per-repository personal settings for a preference that should apply only to the current committed repository without changing its committed files:
shipper settings set generatedBranchPrefix feature/ --personalshipper settings set defaultBaseBranch integration --personalshipper settings set baseIntegration merge --personalshipper settings set sharedCache false --personalshipper settings set reviewDiffCeilingBytes 750000 --personalPersonal settings work for GitHub repositories that you cannot modify, including repositories where branch protection prevents you from committing settings. They also work for committed unlinked and non-GitHub repositories. This makes it possible to configure a third-party repository without owning it or changing its committed files. A repository needs at least one commit to have a stable personal identity.
Shipper selects and creates the personal settings file. Use shipper settings list to discover the
resolved user, repository, and personal paths and their status; do not construct a personal path
yourself.
Setup-written settings
Section titled “Setup-written settings”Setup records selected or discovered intent instead of copying every runtime default into a file.
Personal setup records its selected agent in commands.default.agent and includes installCommand
only when setup discovered or explicitly confirmed one. Fresh repository setup records
cliVersion and an optional discovered or confirmed installCommand, but it does not record an
agent. Existing repository setup preserves an explicit agent pin and every other setting while
refreshing only cliVersion; personal setup may update its selected agent and preserves an existing
valid defaultBaseBranch or baseIntegration.
Setup neither prompts for nor writes transport, pullRequests, or reviewDelivery. Their
absence remains meaningful and supported.
Shipper does not materialize a default into a committed file or a command flag when the operator has
expressed no preference. In particular, setup does not write reviewDiffCeilingBytes unless the
operator explicitly supplied it.
Inspect settings
Section titled “Inspect settings”Use the settings command to inspect effective configuration and provenance:
shipper settingsshipper settings listshipper settings list --jsonshipper settings get commands.default.agentshipper settings get mirrorMode --jsonshipper settings get baseIntegrationshipper settings get reviewDiffCeilingBytesBare shipper settings and shipper settings list have identical human output. list includes
every canonical dotted leaf, configured per-stage command fields, and individual
worktreeEnv.<NAME> entries. Each row shows its effective value and origin (default, user,
repo, personal, footprint, or inferred). It also reports file states and resolver warnings.
get reports the effective value, origin, and raw layer slots, so an absent value remains distinct
from JSON null. Read commands require neither GitHub authentication nor a GitHub-linked
repository. See the full shipper settings CLI reference for output and
flag details.
Edit settings
Section titled “Edit settings”Set or remove one dotted key with exactly one explicit scope:
shipper settings set hookTimeoutMinutes 15 --usershipper settings set mirrorMode mirror-on --reposhipper settings set defaultBaseBranch integration --personalshipper settings set baseIntegration merge --personalshipper settings set sharedCache false --reposhipper settings set reviewDiffCeilingBytes 750000 --personalshipper settings unset sharedCache --personalshipper settings set worktreeEnv.NPM_CONFIG_CACHE '".npm-cache"' --personalshipper settings unset commands.implement.model --personalshipper settings unset defaultBaseBranch --personalshipper settings unset baseIntegration --personal--user, --repo, and --personal are mutually exclusive, and one is mandatory. Values are
parsed as JSON first; when JSON parsing fails, Shipper uses the original text as a string. Thus
15, false, arrays, objects, and quoted strings retain their JSON types, while a bare value such
as mirror-on becomes a string.
Use shipper settings switch <default|stage> <agent> to submit an agent, model, and reasoning
effort as one verified change in any of these scopes. For example,
shipper settings switch implement codex --repo --model o3 --reasoning-effort high verifies the
final combination before saving it. Omit --model or --reasoning-effort to retain its resolved
behavior, or use --clear-model or --clear-reasoning-effort to remove that scope’s override.
See the switch command reference for the full options.
Before writing, Shipper validates the selected layer, the complete effective settings, and
cross-setting repository policy. A successful edit atomically replaces only the selected document
and reports the resulting effective value and origin. unset removes only that scope’s raw value,
revealing the next applicable layer or default. Missing user-global and personal settings files are
created as needed. Repository edits require shipper setup --repo to have created
.shipper/settings.json first.
When a user-global, repository, or personal edit changes an effective agent, model, or reasoning
effort tuple, Shipper probes the exact resulting tuple with the resolved agent for every affected
stage. The requested target of a switch is also verified, even when its effective tuple is
unchanged. Distinct tuples run concurrently within one 30-second overall deadline that includes
command preparation. A rejected value or a probe that cannot run because the agent is missing,
unauthenticated, offline, or too slow refuses the CLI edit and leaves the selected settings file
unchanged. The diagnostic identifies the stage, resolved agent, offending value, and what the agent
reported.
Use --skip-agent-verification on settings set or settings unset only when you deliberately
need to store the change without a passing probe; the CLI reports that bypass explicitly. Desktop
has no save-anyway equivalent. Run shipper settings check (or add --json) to probe all currently
stored effective tuples on demand. Rejections name the settings file and dotted key that supplied
the value. Inspection commands and opening Desktop settings do not run this check automatically.
cliVersion, mirrorMode, transport, pullRequests, and reviewDelivery require --repo
because they are repository-only.
defaultBaseBranch accepts --repo or --personal and rejects --user. Unsetting its personal
value reveals the committed repository value, or GitHub auto-detection when no committed value
exists. After repository setup creates .shipper/settings.json, repository edits work without
GitHub authentication or linkage. Repository setup supports both the GitHub-backed route and a
committed no-transport repository. An origin on a non-GitHub host remains unsupported by
repository-target setup. Personal edits work for any committed repository with a stable identity.
baseIntegration also accepts --repo or --personal and rejects --user. Unsetting a personal
value reveals the committed strategy, or the default "rebase" when the repository omits it.
sharedCache accepts --user, --repo, or --personal. Its precedence is personal > committed
repository > user-global > the default true. A user-global false disables sharing machine-wide
unless a repository or per-repository personal value explicitly enables it again.
reviewDiffCeilingBytes accepts --user, --repo, or --personal, with the same ordinary
personal > repository > user-global > default precedence. It must be a finite positive whole number
of bytes. There is no maximum, warning threshold, or unlimited, off, or disabled sentinel.
The ceiling applies to the complete input after the fixed 50,000-byte per-file reduction for GitHub
review, local review, and implementation review-rework. To guarantee a remedy for the current
repository, use
shipper settings set reviewDiffCeilingBytes <bytes> --personal.
As rough, non-binding sizing guidance, 500,000 diff bytes was estimated at approximately 135,000 tokens. Content and model variance is large, and Shipper does not validate, warn, or cap values from that estimate.
Settings schema
Section titled “Settings schema”| Setting | Default | Description |
|---|---|---|
prReviewWait | { "mode": "checks", "minDurationMinutes": 15, "maxDurationMinutes": 60 } | PR review pre-flight wait strategy. Timer mode uses { "mode": "timer", "durationMinutes": 30 } and waits for pull-request age. The built-in checks policy has a 15-minute minimum and a finite 60-minute maximum: settled checks let review proceed after the minimum, while checks still pending at the maximum are passed to review in the latest rollup. Explicit checks policies accept minDurationMinutes and an optional maxDurationMinutes; omitting the maximum waits indefinitely. After the no-check grace, CI is explicitly unavailable. Foreground review and Ship wait before reviewer launch, while background Auto-ship parks and redispatches when the current revision is ready. Failed-check reruns use an independent clock from each request: timer mode uses 60 minutes; checks mode has a 60-minute minimum, honors longer maxima, and is unbounded when an explicit policy omits its maximum. Original deadlines survive retries. No reviewer launches while any requested rerun is pending or unverifiable, even in mixed pending/failed batches. Finite expiry retains PR Open: manual review and Ship return, while Auto-ship defers for the current pass without an agent run and continues other issues. Retry shipper pr review S#42, shipper ship S#42, or a later Auto-ship run; the same revision observes the existing request. Inspect Actions after ambiguous dispatch and request the rerun there if necessary. All-known-outcome failures are must-fix and reach Rework even with reviewer approval or unavailable/contradictory aggregate evidence. All-green batch decisions count exactly once toward two lifetime deferrals. Ordinary first-run and local-only behavior remain unchanged. |
agentTimeoutMinutes | 60 | Agent process timeout for headless runs in minutes. Set to 0 to disable the timeout. This is an overridable preference. |
hookTimeoutMinutes | 10 | Hard timeout in minutes for file-based hooks and the worktree dependency-install command. Set to 0 to disable enforcement. Timeout aborts for pre-<stage>, worktree-setup, and the install command; timeout warns and continues for post-<stage> and worktree-teardown. This is an overridable preference. |
reviewDiffCeilingBytes | 500000 | Finite positive whole-byte ceiling for the complete reduced review diff. It is eligible at user-global, repository, and per-repository personal scope under ordinary precedence. There is no maximum, warning threshold, or unlimited/off/disabled value, and setup does not materialize the default. The fixed 50,000-byte per-file reduction remains separate. |
groomMaxQuestionRounds | 10 | Maximum question rounds per headless groom session before the final compilation turn. |
generatedBranchPrefix | "shipper/" | Prefix prepended verbatim to generated issue and intake branches. Include a trailing / when desired; an empty string removes the prefix. |
conventions | "shipper" | Commit and pull-request posture. Valid values are "shipper" and "native". |
autoResolveReviewThreads | true | Implementation review-rework replies are always delivered. When enabled, successfully replied-to local findings and GitHub PR threads are resolved at their respective destinations; when disabled, neither is resolved. Resolution failures warn without failing the run. |
adversarialDesignReview | false | Enable the design-stage-only designer → adversary → designer review loop. This is an ordinary overridable preference, not repository-only policy. |
assignMirroredIssuesToSelf | false | Best-effort opt-in to add the current GitHub account to each newly created or adopted mirrored issue. This is an ordinary overridable preference and has no effect in store-only repositories. |
commands | { "default": { "agent": "claude" } } | Optional in source documents. Resolved settings receive the defaults-layer map, and per-stage overrides can set agent, mode, model, reasoningEffort, or disableMcp for prompt-running stages. Hyphenated CLI stage names use underscores in settings. |
commands.default.agent | "claude" | Optional in source documents and always present after resolution. Absence from committed settings means the repository expresses no agent policy; a more specific eligible preference or the built-in defaults-layer "claude" value applies. Supported values are "claude", "codex", "copilot", and "opencode". |
commands.<stage>.agent | unset | Per-stage coding agent override. |
commands.default.model | unset | Default model override for all supported prompt-running stages. |
commands.<stage>.model | unset | Per-stage model override. |
commands.default.reasoningEffort | unset | Default reasoning-effort value for prompt-running stages. Values must be non-empty strings. |
commands.<stage>.reasoningEffort | unset | Per-stage reasoning-effort preference. A one-run --reasoning-effort flag can override it for one invocation. |
commands.default.mode | "default" | Default execution mode for prompt-running stages: "headless", "interactive", or "default". |
commands.<stage>.mode | unset | Per-stage execution mode override. |
commands.default.disableMcp | unset | Default MCP loading policy for all prompt-running stages. When set it applies to every one of them, including setup and new; when unset the shipped per-stage defaults apply, so there is no single scalar default to report and shipper settings list shows this key as <unset>. |
commands.<stage>.disableMcp | disabled for groom, design, plan, implement, and pr_review; enabled for new and setup | Per-stage MCP loading toggle, resolved by one precedence rule with no stage exception: a one-run --disable-mcp / --enable-mcp flag beats all settings, an explicit per-stage value beats an explicit commands.default.disableMcp, and an explicit value at either scope beats the shipped default. The setup / new carve-out is a shipped default only, not a protected behavior, so an explicit default-scope value reaches those stages too. The adversarial design review resolves its policy through design. Read a stage’s effective value with shipper settings get commands.<stage>.disableMcp. An enabled stage prints MCP loading enabled for stage <name>.; a disabled stage prints no MCP policy line. |
defaultBaseBranch | auto-detected from GitHub | Pull-request target eligible in committed repository and per-repository personal settings, but not user-global settings. Personal intent wins over committed intent; Shipper derives the branch from GitHub only when both eligible layers omit it. |
baseIntegration | "rebase" | Feature-branch update strategy: "rebase" or "merge". Eligible in committed repository and per-repository personal settings, but not user-global settings. Personal intent wins over committed intent. |
sharedCache | true | Share Shipper-owned npm, Turbo, uv, pnpm, Cargo, Playwright, Electron, and electron-builder caches machine-wide. Eligible at user-global, committed repository, and per-repository personal scopes; precedence is personal > repository > user-global > default. |
installCommand | unset | Shell command used to install dependencies in a new worktree. "" deliberately disables bootstrap. Compound commands are supported and run through the platform shell. |
mirrorMode | inferred | Repository-only issue-state mode: "store-only" or "mirror-on". When no committed value exists, Shipper derives it from the local mirroring footprint and otherwise uses "store-only". |
transport | inferred | Repository-only transport policy: "origin" or "none". When absent, Shipper selects "origin" only when a remote named exactly origin exists; it does not probe reachability. |
pullRequests | inferred | Repository-only pull-request policy: "github" or "none". When absent, it follows effective transport: "none" for no transport and "github" otherwise. |
reviewDelivery | "local" | Repository-only review-delivery setting whose only value is "local"; Shipper keeps every review internally. With pull requests enabled, implementation review-rework consumes the pinned held round, retains all PR conversation and inline feedback, and routes replies by origin. |
cliVersion | no recorded pin | Repository-only CLI fingerprint written by shipper setup --repo. When no committed value exists, Shipper has no recorded pin. |
worktreeEnv | unset | Environment variables passed through as configured inside worktree execution and merged with built-in cache-directory defaults. |
merge.requirePassingChecks | true | Require no reported CI check to fail before auto-merge. Pending checks defer the merge under merge.pendingChecksTimeoutMinutes rather than blocking it, including when this setting is false. No reported checks after the startup grace count as an empty passing set except during a gated check-registration wait following a Shipper-accepted branch update. |
merge.pendingChecksTimeoutMinutes | 60 | Finite positive number of total in-session minutes Shipper may defer a merge while checks remain pending for one PR head SHA. Repeated observations of that SHA retain the original clock; a changed SHA resets it. For a Shipper-updated head whose old revision reported checks and whose checks gate merging, the same period starts at the first known-empty observation of the published updated SHA and survives later one-shot observations. Registration expiry retains Ready with a revision-specific deferral; exhaustion of already reported pending checks still follows the ordinary issue-failure path. This is independent of prReviewWait, and 0 is invalid rather than disabling the bound. |
For one compatibility release, existing commands.pr_open and commands.pr_remediate keys are
accepted on load but ignored with one warning naming the settings file. Move equivalent agent,
model, mode, reasoning, or MCP overrides to commands.implement; Shipper never copies a retired
value automatically.
With sharedCache: true, Shipper applies these built-in locations:
| Variable | Shared location | Worktree-private fallback |
|---|---|---|
NPM_CONFIG_CACHE | ~/.shipper/cache/shared/npm/ | .shipper/tmp/.npm-cache/ |
TURBO_CACHE_DIR | ~/.shipper/cache/shared/turbo/ | .shipper/tmp/.turbo-cache/ |
UV_CACHE_DIR | ~/.shipper/cache/shared/uv/ | .shipper/tmp/.uv-cache/ |
PNPM_CONFIG_STORE_DIR | ~/.shipper/cache/shared/pnpm/ | .shipper/tmp/.pnpm-store/ |
NPM_CONFIG_STORE_DIR | same pnpm store (pnpm 10 alias) | .shipper/tmp/.pnpm-store/ |
CARGO_HOME | ~/.shipper/cache/shared/cargo/ | .shipper/tmp/.cargo-home/ |
PLAYWRIGHT_BROWSERS_PATH | ~/.shipper/cache/shared/playwright/ | .shipper/tmp/.playwright-browsers/ |
electron_config_cache | ~/.shipper/cache/shared/electron/ | .shipper/tmp/.electron-cache/ |
ELECTRON_BUILDER_CACHE | ~/.shipper/cache/shared/electron-builder/ | .shipper/tmp/.electron-builder-cache/ |
XDG_CACHE_HOME | never shared | .shipper/tmp/.cache/ |
The general XDG_CACHE_HOME stays worktree-private so unnamed tool state, credentials, and
configuration do not carry between stages. A string-valued covered variable in effective
worktreeEnv is operator-owned, remains byte-for-byte unchanged, and is not granted sandbox access
by Shipper. Shipper sets both pnpm config aliases because pnpm 10 reads the npm-style name while
pnpm 11 reads the pnpm-specific name; an operator value for either alias makes the pnpm store
operator-owned and suppresses both redirects. Shipper emits one aggregate informational notice
when sharing is enabled. It names every operator-owned setting that prevents sharing and states
that removing it restores Shipper sharing.
Sandboxed temporary storage is sandboxed-only. For a sandboxed Codex child on macOS, Linux, and WSL2, Shipper applies TMPDIR, TMP, and TEMP
after the inherited environment and all worktreeEnv values are resolved. Each receives a fresh
unique real /tmp/shipper-cx-<24 lowercase hex> directory (shown as /private/tmp/... after macOS
canonicalization) so Unix-domain socket path headroom remains. An operator value cannot replace the
sandboxed Codex child’s temporary location. There is no setting, flag, or opt-out for this
precedence. It is scoped to the spawned sandboxed Codex process: unsandboxed runs, sandboxed Claude
(which uses native per-session temporary storage with no Shipper override), other agents, and the
operator’s shell remain unchanged.
Shipper deletes current and abandoned sandboxed Codex directories only when exact ownership is
proven. Matching path, owner, device, and inode is insufficient because a successor directory may
reuse them. For abandoned reclamation, both the Shipper process and Codex child must also be proven
dead. When exact ownership cannot be proven, Shipper preserves the candidate, warns with its
specific location, and continues the requested stage with fresh isolated storage. Preparation
failure that makes isolated storage unsafe prevents launch with a standalone temporary-storage
error. Cleanup failures warn with the location and do not change the stage result or fall back to
shared or uncertain storage. Shipper does not add --add-dir /tmp or promise exact-directory
confinement; Codex may independently permit other system temporary directories.
Setting sharedCache: false keeps every Shipper-owned cache default private without an override or
degradation message. This switch controls a performance optimization and is therefore configurable,
unlike the non-configurable worktree safety recovery introduced by S#240 and S#201. Shared-cache
preparation degrades per cache: an unresolvable, uncreatable, insecure, or unwritable child reports
one informational reason and falls back alone while healthy siblings remain shared. Failure of the
common root reports once and makes every covered cache private. Neither case fails a stage.
When an install fails after using a marked shared cache, Shipper first falls back to private paths
and leaves the shared cache intact for concurrent worktrees. Location-based recovery then still
applies to private and custom caches: a custom cache placed strictly below the affected worktree’s
.shipper/tmp/ directory is treated as regenerable, so Shipper clears only that named location and
retries the install once. A custom cache placed elsewhere is not cleared automatically. The
effective NPM_CONFIG_CACHE, TURBO_CACHE_DIR, UV_CACHE_DIR, PNPM_CONFIG_STORE_DIR,
NPM_CONFIG_STORE_DIR, CARGO_HOME, PLAYWRIGHT_BROWSERS_PATH, electron_config_cache,
ELECTRON_BUILDER_CACHE, and XDG_CACHE_HOME locations remain eligible anywhere they safely
resolve inside the worktree. This includes operator-configured covered variables outside
.shipper/tmp/, provided they remain inside the canonical worktree. Recovery never clears
.shipper/tmp/ wholesale, so other Shipper runtime state there is preserved.
First use starts cold and warms through ordinary tool downloads. Shipper never reads, links, or
copies npm, uv, pnpm, Cargo, Playwright, Electron, or electron-builder data from the operator’s own
cache to seed these locations. Normal runs never evict, prune, size-cap, or delete the active or
legacy shared cache trees; only explicit shipper cache clear does so. Tools may still perform
their own documented cache maintenance; Shipper does not control tool-owned garbage collection.
The concurrency basis is the tool’s existing behavior rather than a Shipper-side lock:
| Cache | Basis for concurrent sharing |
|---|---|
| npm | npm already uses one machine-global package cache for unrelated projects. |
| Turbo | Cache entries are keyed by task inputs and Turbo supports cache sharing across repositories and machines. |
| uv | uv already uses one machine-global download/cache location for unrelated projects. |
| pnpm | Its content-addressed store is already machine-global; Shipper shares it only within the same operator and trust domain. |
| Cargo | Cargo already uses one machine-global package cache and protects it with its own package-cache process locks. |
| Playwright | Playwright already uses one machine-global browser registry for unrelated projects and protects registry operations with its own __dirlock. |
| Electron | Electron already uses one machine-global runtime download cache for unrelated projects. |
| electron-builder | electron-builder already uses one machine-global packaging-tool cache for unrelated projects. |
The reviewed macOS Playwright 1.62.1 concurrency experiment ran two simultaneous cold Chromium installs. Both completed successfully and produced one complete browser/tool set with completion markers. No corruption was demonstrated. Playwright therefore ships shared on tool-native safety, and Shipper adds no coordination or cache lock.
A repository that needs Playwright browsers installs them through its own install or test command;
Shipper adds no browser-install step, pre-warm, or fallback to the operator’s default registry. This
matches the isolation behavior Shipper already produces on Linux. The cold download cost is once per
machine instead of once per worktree. To reuse or isolate an existing browser registry, set
worktreeEnv.PLAYWRIGHT_BROWSERS_PATH to that location; the operator-owned value is left unchanged.
Redirecting CARGO_HOME shares the whole Cargo home, not only registry downloads. Its bin/
directory and .crates.toml / .crates2.json install ledgers are machine-wide and agent-writable,
so cargo install results and cargo-* subcommands can be replaced or executed across unrelated
repositories. The operator’s own config.toml, credentials.toml, and private registry setup are
not visible inside worktree execution. Configure worktreeEnv.CARGO_HOME explicitly as the
supported isolation escape hatch. Place that override outside the worktree if install-failure
recovery must never clear it. The value remains operator-owned, and Shipper neither redirects it nor
grants an arbitrary operator path to the agent sandbox.
Playwright browsers, Electron runtime archives, and electron-builder’s packaging and
signing-adjacent tools are likewise agent-writable executable content that stages for unrelated
repositories may later execute. Sharing remains within one operator’s trust domain. The supported
per-cache isolation escape hatches are worktreeEnv.PLAYWRIGHT_BROWSERS_PATH,
worktreeEnv.electron_config_cache, and worktreeEnv.ELECTRON_BUILDER_CACHE; each operator-owned
value remains unchanged and is not granted sandbox access by Shipper.
The two merge leaves resolve independently across settings layers:
merge.requirePassingChecks and merge.pendingChecksTimeoutMinutes each use the most-specific
eligible value that defines that leaf. Other object-valued settings such as prReviewWait use
whole-value replacement. The pending-check timeout measures merge deferral elapsed time; the PR
review wait controls review-stage timing and does not extend, shorten, or disable that bound.
Merge method, fallback, behind-branch update mode, and post-merge remote deletion are resolved automatically and expose no setting. See the canonical merge behavior description.
For defaultBaseBranch, a per-repository personal value wins over the committed value, which wins
over GitHub auto-detection. For baseIntegration, a per-repository personal value wins over the
committed value, which wins over the "rebase" default. For the five repository-only settings,
fallback behavior is derived only when the applicable committed value is absent. These fallbacks
are not persistent settings layers.
Valid commands.*.agent values are "claude", "codex", "copilot", "grok", and
"opencode". Claude Code reads CLAUDE.md; Codex CLI, GitHub Copilot CLI, Grok Build, and
OpenCode read AGENTS.md.
Adversarial design review
Section titled “Adversarial design review”adversarialDesignReview controls adversarial review of the design stage only. It is off by
default, with the unconfigured false value and its provenance reported as the built-in default.
When enabled, one design run performs exactly designer → adversary → designer. Shipper records
both interim artifacts as issue notes so each following participant can see the current design or
critique. The round count is fixed at one; there is no setting for additional rounds or a separate
adversary agent or model.
The loop uses roughly three agent invocations instead of one, with correspondingly higher cost and latency. Shipper announces active adversarial review at the start of the design stage. An adversary failure fails the stage instead of silently accepting the initial designer artifact.
The setting uses ordinary most-specific-scope-wins precedence at user-global, committed repository, and per-repository personal scopes. Inspect, set, and clear it through the normal settings commands:
shipper settings get adversarialDesignReviewshipper settings set adversarialDesignReview true --personalshipper settings unset adversarialDesignReview --personalAfter unset, inspection reports the inherited scope or the built-in false default and its
provenance. The desktop repository-settings dialog offers the same personal set/clear control and
shows the effective value and source.
Assign mirrored issues to yourself
Section titled “Assign mirrored issues to yourself”assignMirroredIssuesToSelf is an opt-in ordinary preference that defaults to false. It is
eligible at user-global, committed repository, and per-repository personal scopes, with normal
most-specific precedence: personal > repository > user-global > default.
When it is enabled in a mirror-on repository, Shipper adds the authenticated GitHub account with
the additive @me operation after creating an intake issue, grooming prerequisite, follow-up, or
decomposition child, an adoption or PR tracking issue, or a missing mirror during reconciliation.
This preserves every existing assignee. GitHub treats assigning an account that is already present
as idempotent. Bulk adoption applies the same behavior independently to every newly adopted issue.
Assignment is write-once creation behavior, not synchronized issue state. Shipper does not compare, persist, remove, restore, or otherwise reconcile assignees. Removing yourself later produces no drift, and reconciliation does not retry assignment for an existing mirror.
The setting has no effect in store-only repositories because those workflows do not create or
mutate GitHub issue mirrors. If assignment fails after issue creation or adoption succeeds, the
workflow remains successful and managed. Shipper reports an issue-specific warning with the GitHub
reason and does not retry automatically.
Inspect, enable, or clear the preference through the settings command:
shipper settings get assignMirroredIssuesToSelfshipper settings set assignMirroredIssuesToSelf true --usershipper settings set assignMirroredIssuesToSelf true --personalshipper settings unset assignMirroredIssuesToSelf --personalThe desktop repository-settings dialog exposes the same per-repository personal override and shows its effective value and source.
One-run overrides
Section titled “One-run overrides”Prompt-running commands can override agent, model, mode, reasoning effort, and MCP loading
for one invocation with flags such as --agent, --model, --mode, --reasoning-effort,
--disable-mcp, and --enable-mcp. These invocation controls are separate from persistent
settings.
Direct shipper design runs can override adversarial review in either direction with
--enable-adversarial-review or --disable-adversarial-review. These mutually exclusive flags take
precedence over adversarialDesignReview for that invocation only and do not persist or change any
settings file. Other workflow stages do not expose adversarial review.
Reasoning effort for one run uses the invocation flag when present, then the stage preference, then
commands.default.reasoningEffort. When all are unset, Shipper preserves the selected agent’s
native default. Persistent changes are verified with the real resolved agent rather than an
allowlist. Claude Code and OpenCode can accept an effort while silently using a default; Shipper
writes that value with a warning that it could not be confirmed and may be ignored. Codex CLI,
GitHub Copilot CLI, and Grok Build provide a definitive effort rejection, which refuses the
persistent change.
One-run values are carried directly to the actual workflow invocation.
Model resolves the same way: the --model invocation flag when present, then the stage preference,
then commands.default.model. When all are unset, Shipper imposes no model and preserves the
selected agent’s native default — Claude Code resolves whichever model you have configured as your
own default, and Codex CLI, GitHub Copilot CLI, Grok Build, and OpenCode do the same. When a value
does resolve, Codex CLI receives it as -m under exec (after --json) when that subcommand is
present, or at the start of its arguments otherwise; a global -m would make Codex discard the
configured reasoning effort. Claude Code, GitHub Copilot CLI, Grok Build,
and OpenCode receive it as an appended --model.
Generated branch prefix
Section titled “Generated branch prefix”generatedBranchPrefix is an overridable preference. A personal value can therefore change branch
refs for a third-party repository without modifying that repository.
Shipper uses the configured string verbatim and adds no separator. Before a branch-aware flow runs, Shipper validates representative names with Git. An invalid value fails settings loading and names the setting and configured value.
Change this setting only when no work is in flight. Shipper does not rename branches or remember an earlier custom prefix, so finish or reset affected work before selecting a different prefix.
Repository policy axes
Section titled “Repository policy axes”transport and pullRequests are independent repository-only policy axes. Their exact values are
origin|none and github|none, respectively. When omitted, Shipper resolves them in dependency
order on every run. See
Repository postures for the operating picture produced by these
values.
transportis"origin"when a Git remote named exactlyoriginis present and"none"otherwise. Other remote names do not count. Resolution reads onlygit remote; it performs no URL lookup, authentication, reachability check, or network request.pullRequestsis"none"when effective transport is"none"and"github"otherwise.
reviewDelivery is not a policy axis: its only value is "local", whether or not the key is set.
Inferred values are never written back. Configured values report the committed settings path as
their origin; inferred values report the remote presence or effective dependency they followed.
Policy resolution rejects configured transport: "origin" without an origin remote,
transport: "none" with pullRequests: "github", and transport: "none" with
mirrorMode: "mirror-on". Effective,
including inferred, values participate. All violations are reported together in deterministic
order with both values, their origins, and repair guidance.
These rules apply everywhere repository policy is already resolved, including indirect
issue-projection callers. They do not add policy resolution to commands that do not use it.
shipper settings list and get remain non-throwing diagnostics for an invalid committed policy.
Repository-scoped set and unset validate the complete candidate before an atomic write, so a
correcting edit succeeds while an edit that leaves the policy invalid is refused.
Base integration
Section titled “Base integration”baseIntegration governs every Shipper-owned integration of base-branch or remote feature-branch
updates. The default "rebase" preserves existing behavior: feature commits can be replayed and a
recovered non-fast-forward publication may use force-with-lease. "merge" instead preserves
existing feature commit IDs, creates a merge commit only when the integrated ref is not already
contained, and publishes only through ordinary fast-forward updates. A merge conflict gets one
bounded three-attempt resolution episode; exhaustion aborts the merge and restores the exact
pre-integration branch and tracked worktree state.
The tradeoff is visible branch history: merge mode can add merge commits to the feature branch.
Pull-request completion is unchanged and remains automatically selected by Shipper’s existing
merge-method capability logic, including squash completion when that is the resolved method.
Local-delivery behavior is also unchanged. Shipper never infers a force-push prohibition from a
rejected push and never changes baseIntegration automatically.
This synchronization strategy is orthogonal to the three named repository postures. It applies within every posture and does not create additional posture combinations.
Review delivery
Section titled “Review delivery”Shipper validates every review and keeps it internally as one locally held review round, recording
the agent’s intended disposition unchanged. It makes no pull-request mutation: no review, inline
review comment, pull-request comment, pointer, or placeholder. Under mirrorMode: "mirror-on", the
review stage’s ordinary issue-summary comment and label transition are still mirrored to the GitHub
issue. When pull requests are enabled, review acquisition still reads the GitHub PR. When pull
requests are disabled, acquisition reads the pinned local three-dot diff instead.
A committed reviewDelivery: "post" is accepted for this release only and treated as "local".
Every command, shipper status, MCP shipper_status, and the Desktop repository settings dialog
show a warning, and Shipper never rewrites the committed file. Remove the key or set it to
"local"; support for "post" ends in the next release.
shipper settings set reviewDelivery post --repo is refused because reviews are always kept
internally.
Each local review is a durable, write-once record keyed by local issue and review round. It contains
the reviewed acquisition revision, feedback body, intended disposition, optional pull request
number, stable IDs and complete path/line/side/range data for every inline finding, and structured
addition/deletion counts for files omitted from review because of diff size. Stage output and the
issue-facing summary name the held round and state that Shipper keeps it internally. Compact rounds and
complete individual rounds are browseable through the CLI, desktop, and MCP issue surfaces; see
shipper issue view for CLI selection.
Review pre-flight reads the exact held round pinned by current review/rework history without
selecting or superseding another round. Implementation review-rework keeps that already-pinned
round and stores replies and resolution state locally when pull requests are disabled. With a
GitHub PR, it also retains every PR conversation comment and inline thread, and replies are posted
only when that capability is enabled. Author identity is never used to classify ownership. The run
output identifies the pinned round and reports consumed findings plus locally recorded reply and
resolution counts. See shipper pr review and
shipper implement for acquisition and rework details.
CLI startup under local transport
Section titled “CLI startup under local transport”The shared protected-command startup path branches on effective transport. With effective
transport: "none", Shipper does not require the GitHub CLI to be installed or authenticated and
does not perform a GitHub repository lookup. This applies uniformly to commands covered by shared
startup; commands do not opt into a separate preflight path.
Local transport removes only the GitHub-specific startup work. The existing order remains:
settings loading, CLI version-freshness enforcement, license enforcement, stable repository
identity, and local prerequisite setup. Licensing still applies to read-only commands; there is no
read-only or per-command license exemption. shipper issue view retains its cached-only license
validation. The existing startup-exempt command set is unchanged, and a committed Shipper
configuration without its required CLI fingerprint still stops with setup guidance.
After local startup, each operation validates GitHub only when its effective transport, pull request, or issue-projection capability needs GitHub. Local issue creation and workflow stages, local change packaging and review, implementation review-rework, and local merge therefore continue without a remote. Capability-specific operations remain unavailable when their capability is disabled: GitHub issue adoption and projection; PR creation recovery, inspection, and checks; and remote publication. Their diagnostics name the disabled effective capability rather than imposing a blanket local-transport refusal.
Stable repository identity
Section titled “Stable repository identity”Repository identity is independent of transport. When a repository name can be selected from local
configuration—GH_REPO, a GitHub CLI default-remote marker, or a network origin URL—Shipper uses
the normalized owner/name without a network request. Therefore an origin-backed repository that
sets transport: "none" keeps the same identity and can read its existing issue history, sessions,
and usage.
GH_REPO has the highest precedence and therefore selects the identity and storage key for every
repository-keyed surface in that Shipper process, not only the repository displayed by issue
commands. Avoid exporting one fixed GH_REPO value globally, because doing so would intentionally
make otherwise unrelated checkouts share personal settings, issue history, sessions, and usage.
When no name is determinable, Shipper derives a local identity from commit lineage. A complete
single-root history uses root-<oid>; multiple roots use
roots-<sha256-of-sorted-root-oids>; shallow or incomplete history uses head-<oid>. A repository
must have an initial commit before protected commands can use this identity. Checkout paths and
generated placeholder IDs are never used.
Issue history, agent sessions, usage totals, and per-repository personal settings all use the same
storage key. Named repositories retain the existing flat owner-repo key. Lineage identities use
unlinked/<lineage-token>. Existing data in the former machine-wide _unlinked directories is
left untouched: Shipper does not migrate, read through, rewrite, or delete it.
Before Shipper 4.2, a non-GitHub network origin used a lineage-keyed personal-settings path such
as ~/.shipper/config/unlinked/root-<oid>/settings.json. It now uses the locally determinable
repository name and flat key. Shipper does not migrate or fall back to the former personal-settings
file; use shipper settings list to locate both paths and copy any settings you still want.
The lineage boundary is intentionally shared. Checkouts, clones, worktrees, and projects that share the same root commit history resolve to the same identity. This includes two projects created from the same template commit. When two such projects must be isolated, create a new independent root commit and history for one project.
Conventions posture
Section titled “Conventions posture”conventions: "shipper" preserves Shipper’s commit scopes, pull-request issue markers, and
validation. For an actual pull request, the first eligible automatic host template frames the body
when one exists; otherwise Shipper uses its established generic body structure. Template framing is
always on and has no setting or opt-in. Shipper’s mirror-mode linkage and optional Agent Feedback
remain present within the frame. Prepared local change descriptions continue to use the established
Shipper structure even when the repository contains an automatic template.
conventions: "native" tells agents to follow the host repository’s explicit guidance and recent
history. Its title and body restrictions are unchanged: native pull requests omit Shipper’s local
issue marker and Agent Feedback section, and prohibit GitHub closing references in the pull-request
body and commits.
Native posture is valid only with effective mirrorMode: "store-only". Shipper rejects native
posture combined with mirrorMode: "mirror-on" and reports the source of both effective values. To
move an existing store-only repository onto mirroring without landing in this rejected state, follow
the single ordered procedure in
Graduating a store-only repository to mirroring.
Posture does not affect branch generation; set generatedBranchPrefix separately when desired.
conventions uses ordinary settings precedence, so it can be set at any scope. The desktop
repository-settings dialog edits it at the personal scope; it refuses a personal value whose
resulting effective combination would be native posture with mirrorMode: "mirror-on", and shows
the reason inline next to the control.
Mirror mode
Section titled “Mirror mode”mirrorMode controls whether setup provisions GitHub mirroring artifacts. It is shared repository
policy and must be edited with --repo.
When no committed mirrorMode exists, Shipper derives mirror-on if local issue state shows a
GitHub mirroring footprint, preserving existing mirrored repositories. Without that footprint it
derives store-only. This inference does not write .shipper/settings.json.
To enable mirroring, perform these steps in order:
-
Confirm effective
transportis not"none". Add anoriginremote or settransport: "origin"only when that remote exists. -
Establish the committed repository settings layer:
Terminal window shipper setup --repo -
Set and commit the repository policy:
Terminal window shipper settings set mirrorMode mirror-on --repotransport: "none"withmirrorMode: "mirror-on"is rejected here by repository settings validation, which prints the conflicting effective values. A hand-edited invalid committed file is reported by policy-consuming commands such asshipper status; configure a realoriginbefore selecting origin transport.
New work then creates GitHub mirrors. Existing local-only records are not created in bulk during
the setting change. Use shipper issue reconcile to identify open candidates and
shipper issue reconcile --repair to backfill them deliberately.
Graduating a store-only repository to mirroring
Section titled “Graduating a store-only repository to mirroring”A store-only repository typically runs conventions: "native", which cannot be combined with
mirrorMode: "mirror-on". Enabling mirroring therefore requires both settings to change together,
followed by a deliberate backfill. Perform these steps in order so the repository never passes
through the rejected native + mirror-on state:
-
Establish or refresh the committed repository settings layer:
Terminal window shipper setup --repo -
Confirm effective
transportis not"none". Add anoriginremote or configure consistent origin transport before enabling mirroring. -
Set
conventionstoshipper:Terminal window shipper settings set conventions shipper --repoThen run
shipper settings get conventions. If a higher-precedence per-repository personal value keeps the effective posture atnative, either change that personal value or remove it so the committedshipperposture can take effect:Terminal window shipper settings set conventions shipper --personal# orshipper settings unset conventions --personal -
Set
mirrorModetomirror-on:Terminal window shipper settings set mirrorMode mirror-on --repoRepository settings validation rejects this command if effective transport is
"none"and prints both conflicting effective values. A hand-edited invalid committed file is reported by policy-consuming commands such asshipper status; a realoriginmust exist before selecting origin transport. -
Backfill mirrors for pre-existing local-only issues:
Terminal window shipper issue reconcile --repair
The backfill preserves each record’s local identity and workflow history. A remembered
same-repository issue origin reconnects when it is open, unclaimed, and carries no shipper: label;
the store then projects its authoritative content and workflow history, and assignment follows the
adoption assignment setting. PR-only, foreign, and origin-free records receive fresh mirrors.
Closed, missing, labeled, or claimed origins receive no replacement and are reported individually,
so inspect the per-record check and repair output.
See the shipper issue reconcile reference for the full backfill
semantics.
To disable mirroring, run and commit:
shipper settings set mirrorMode store-only --repoWhile store-only is active, existing GitHub mirrors remain frozen and new work remains local-only. Re-enabling mirroring makes those projections active again; reconcile realigns them from the local store, which remains authoritative.
Failure handling
Section titled “Failure handling”Committed repository settings are strict: an unreadable or malformed .shipper/settings.json
fails settings loading. A missing, unreadable, or malformed personal file contributes no personal
value while retaining its resolved status for inspection.
Repositories with committed Shipper settings warn when the installed CLI is newer than the
recorded cliVersion within the same major version. They fail for a missing or unreadable committed
fingerprint, an installed CLI downgrade, or major-version drift. Run shipper setup --repo and
commit the resulting .shipper/ changes to refresh the fingerprint. Repositories configured only
with user-level settings do not require a committed fingerprint.
The SHIPPER_SKIP_VERSION_CHECK runtime override
is a temporary escape hatch. Development builds using 0.0.0-dev bypass the runtime check. CI and
pre-push protection use the broader setup-drift check, of which cliVersion is one part.
For symptom-first recovery, see Troubleshooting for repository settings changes before setup, stale locks, worktree creation and install command failures, CLI version drift and version mismatch, agent and hook timeouts, and MCP tool loading issues.