Skip to content

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:

  • mirrorMode governs whether the repository projects local issue state to GitHub.
  • cliVersion records the Shipper CLI version expected by the committed configuration.
  • transport selects "origin" or "none".
  • pullRequests selects "github" or "none".
  • reviewDelivery is 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.

Use user-global settings for a preference that should apply to every repository:

Terminal window
shipper settings set commands.default.agent codex --user
shipper settings set agentTimeoutMinutes 0 --user
shipper settings set reviewDiffCeilingBytes 750000 --user

Use committed repository settings for shared team intent. The file lives at .shipper/settings.json, is created by shipper setup --repo, and should be committed:

Terminal window
shipper settings set mirrorMode mirror-on --repo
shipper settings set installCommand 'npm ci' --repo
shipper settings set reviewDiffCeilingBytes 750000 --repo

Use per-repository personal settings for a preference that should apply only to the current committed repository without changing its committed files:

Terminal window
shipper settings set generatedBranchPrefix feature/ --personal
shipper settings set defaultBaseBranch integration --personal
shipper settings set baseIntegration merge --personal
shipper settings set sharedCache false --personal
shipper settings set reviewDiffCeilingBytes 750000 --personal

Personal 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 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.

Use the settings command to inspect effective configuration and provenance:

Terminal window
shipper settings
shipper settings list
shipper settings list --json
shipper settings get commands.default.agent
shipper settings get mirrorMode --json
shipper settings get baseIntegration
shipper settings get reviewDiffCeilingBytes

Bare 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.

Set or remove one dotted key with exactly one explicit scope:

Terminal window
shipper settings set hookTimeoutMinutes 15 --user
shipper settings set mirrorMode mirror-on --repo
shipper settings set defaultBaseBranch integration --personal
shipper settings set baseIntegration merge --personal
shipper settings set sharedCache false --repo
shipper settings set reviewDiffCeilingBytes 750000 --personal
shipper settings unset sharedCache --personal
shipper settings set worktreeEnv.NPM_CONFIG_CACHE '".npm-cache"' --personal
shipper settings unset commands.implement.model --personal
shipper settings unset defaultBaseBranch --personal
shipper 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.

SettingDefaultDescription
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.
agentTimeoutMinutes60Agent process timeout for headless runs in minutes. Set to 0 to disable the timeout. This is an overridable preference.
hookTimeoutMinutes10Hard 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.
reviewDiffCeilingBytes500000Finite 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.
groomMaxQuestionRounds10Maximum 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".
autoResolveReviewThreadstrueImplementation 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.
adversarialDesignReviewfalseEnable the design-stage-only designer → adversary → designer review loop. This is an ordinary overridable preference, not repository-only policy.
assignMirroredIssuesToSelffalseBest-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>.agentunsetPer-stage coding agent override.
commands.default.modelunsetDefault model override for all supported prompt-running stages.
commands.<stage>.modelunsetPer-stage model override.
commands.default.reasoningEffortunsetDefault reasoning-effort value for prompt-running stages. Values must be non-empty strings.
commands.<stage>.reasoningEffortunsetPer-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>.modeunsetPer-stage execution mode override.
commands.default.disableMcpunsetDefault 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>.disableMcpdisabled for groom, design, plan, implement, and pr_review; enabled for new and setupPer-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.
defaultBaseBranchauto-detected from GitHubPull-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.
sharedCachetrueShare 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.
installCommandunsetShell command used to install dependencies in a new worktree. "" deliberately disables bootstrap. Compound commands are supported and run through the platform shell.
mirrorModeinferredRepository-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".
transportinferredRepository-only transport policy: "origin" or "none". When absent, Shipper selects "origin" only when a remote named exactly origin exists; it does not probe reachability.
pullRequestsinferredRepository-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.
cliVersionno recorded pinRepository-only CLI fingerprint written by shipper setup --repo. When no committed value exists, Shipper has no recorded pin.
worktreeEnvunsetEnvironment variables passed through as configured inside worktree execution and merged with built-in cache-directory defaults.
merge.requirePassingCheckstrueRequire 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.pendingChecksTimeoutMinutes60Finite 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:

VariableShared locationWorktree-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_DIRsame 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_HOMEnever 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:

CacheBasis for concurrent sharing
npmnpm already uses one machine-global package cache for unrelated projects.
TurboCache entries are keyed by task inputs and Turbo supports cache sharing across repositories and machines.
uvuv already uses one machine-global download/cache location for unrelated projects.
pnpmIts content-addressed store is already machine-global; Shipper shares it only within the same operator and trust domain.
CargoCargo already uses one machine-global package cache and protects it with its own package-cache process locks.
PlaywrightPlaywright already uses one machine-global browser registry for unrelated projects and protects registry operations with its own __dirlock.
ElectronElectron already uses one machine-global runtime download cache for unrelated projects.
electron-builderelectron-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.

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:

Terminal window
shipper settings get adversarialDesignReview
shipper settings set adversarialDesignReview true --personal
shipper settings unset adversarialDesignReview --personal

After 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.

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:

Terminal window
shipper settings get assignMirroredIssuesToSelf
shipper settings set assignMirroredIssuesToSelf true --user
shipper settings set assignMirroredIssuesToSelf true --personal
shipper settings unset assignMirroredIssuesToSelf --personal

The desktop repository-settings dialog exposes the same per-repository personal override and shows its effective value and source.

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.

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.

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.

  1. transport is "origin" when a Git remote named exactly origin is present and "none" otherwise. Other remote names do not count. Resolution reads only git remote; it performs no URL lookup, authentication, reachability check, or network request.
  2. pullRequests is "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.

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.

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.

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.

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: "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.

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:

  1. Confirm effective transport is not "none". Add an origin remote or set transport: "origin" only when that remote exists.

  2. Establish the committed repository settings layer:

    Terminal window
    shipper setup --repo
  3. Set and commit the repository policy:

    Terminal window
    shipper settings set mirrorMode mirror-on --repo

    transport: "none" with mirrorMode: "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 as shipper status; configure a real origin before 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:

  1. Establish or refresh the committed repository settings layer:

    Terminal window
    shipper setup --repo
  2. Confirm effective transport is not "none". Add an origin remote or configure consistent origin transport before enabling mirroring.

  3. Set conventions to shipper:

    Terminal window
    shipper settings set conventions shipper --repo

    Then run shipper settings get conventions. If a higher-precedence per-repository personal value keeps the effective posture at native, either change that personal value or remove it so the committed shipper posture can take effect:

    Terminal window
    shipper settings set conventions shipper --personal
    # or
    shipper settings unset conventions --personal
  4. Set mirrorMode to mirror-on:

    Terminal window
    shipper settings set mirrorMode mirror-on --repo

    Repository 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 as shipper status; a real origin must exist before selecting origin transport.

  5. 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:

Terminal window
shipper settings set mirrorMode store-only --repo

While 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.

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.