ferry-local runs a single Ferry phase on the operator's machine from a Jira status transition, without using a GitHub Actions runner.
- Reuses the existing
ferry-agentruntime and prompt stack. - Reuses GitHub and Jira credentials already required by Ferry.
- Isolates each ticket in
.ferry-local/worktrees/<KEY>on branchferry/<KEY>. - Refuses the
mergephase locally to preserve the ADR-0005 boundary.
npm run ferry-local -- poll --once
npm run ferry-local -- poll --dry-run --once
npm run ferry-local -- serve --port 8787Published installs expose the same entrypoint as:
ferry-local poll --once
ferry-local serve --port 8787Polling and webhook execution both require the normal Jira credentials:
FERRY_JIRA_BASE_URLFERRY_JIRA_EMAILFERRY_JIRA_API_TOKEN
Webhook mode additionally requires:
FERRY_LOCAL_WEBHOOK_SECRET
The local phase run also needs the same provider and forge credentials that ferry-agent needs for the selected phase, such as GITHUB_TOKEN and your LLM provider key.
ferry-local maps Jira statuses from ferry.config.*:
workflow.agents.refiner.trigger_column→refineworkflow.agents.developer.trigger_column→devworkflow.agents.reviewer.trigger_column→reviewworkflow.agents.iterator.trigger_column→iterateReady to Merge→ refused locally
ferry-local poll searches Jira for tickets in the configured Ferry workflow columns and processes each match once per observed event id. The event id defaults to <updated-millis>-<ticket-key>, which keeps retries idempotent while allowing later transitions to produce a new run.
Use --once for cron-driven execution. Without --once, the process polls every 30 seconds by default. Override with FERRY_LOCAL_POLL_INTERVAL_MS.
ferry-local serve listens for POST requests and expects X-LF-Token to match FERRY_LOCAL_WEBHOOK_SECRET.
Supported payloads:
{
"issue": {
"key": "CHAN-1",
"fields": {
"status": { "name": "In Development" },
"updated": "2026-06-13T09:10:11.000Z"
}
}
}Or the simplified equivalent:
{
"ticket_key": "CHAN-1",
"status": "In Development",
"ts": "2026-06-13T09:10:11.000Z"
}--dry-run prints:
- The synthesized envelope
- The resolved branch
- The resolved worktree path
- The
ferry-agentcommand that would run
No git worktree mutation or agent process is started in dry-run mode.
- The webhook secret is mandatory for
serve. - Secrets are read from env and are never logged by the local runner.
- Each ticket runs in its own worktree to avoid shared working-tree mutation.
- The local runner does not introduce a merge backdoor:
mergeis rejected before invocation.