memoryOne static binary — over your coding CLI

mcptask runner – AI developer on your machine. It finishes tasks to merged PRs.

mcptask_runner turns your coding CLI — Claude Code, Codex CLI or OpenCode — into a self-driving teammate. It works with Claude, Kimi, DeepSeek, local ollama, or any model you choose — no vendor lock-in. It picks the task, writes code and tests, and opens a PR. In auto-squash modes it merges when CI goes green; in manual modes it leaves the PR open for you to review. Assign well-specified work in the evening, find it done in the morning.

verified_userFree 30-day trial. No credit card. Cancel any time.

terminal
mcptask_runnermcptask_runner run today
→ Triage: difficulty=13, model=opus, complexity=high
→ Picking next task#10959 (Nová veřejná stránka o mcptask_runner)
→ Snapshotstarting → triage → processing
→ Branchfeature/10959-runner-public-page
→ CI✓ green
→ PR auto-merged
→ Quota: 18% of daily budget used
1 commandto start
~2 minto first task
infoWhat is it

A full autonomous harness around your coding CLI

mcptask_runner is one static binary with no runtime dependencies — it drives a coding CLI on your machine and runs the whole agent loop: pick a task → triage it → choose a model → run the coding CLI → watch the run with a watchdog → respect the daily quota → (optionally) merge the PR after CI. It works with any project that uses git, on GitHub, on Bitbucket Cloud or on GitLab — nothing in the execution path needs Ruby, bundler or a Gemfile, project metadata comes from CLAUDE.md, and `bin/ci` is a convention (no `bin/ci` → skip). No prompt it composes names a framework's command any more: a project that needs preparing before its browser suite declares that itself — in its CLAUDE.md, or in the command it hands `/test-runner`.

auto_awesome

It is not just a 'dumb launcher' for the coding CLI. It adds orchestration, supervision, recovery, and quality control that a bare CLI does not have.

tuneYour coding CLI

Pick the CLI that drives your project

The harness is the coding CLI the runner drives. Three profiles ship in the binary — Claude Code, Codex CLI and OpenCode — and a custom one lives in ~/.mcptask/harnesses/. The choice is made once per machine per project by init, which writes harness: into config/mcptask_runner.yml. That file is per-machine and git-ignored, so two developers on the same repository can drive different CLIs.

terminalThe one command
mcptask_runner init --cli codex

layersThe three bundled profiles

auto_awesome

Claude Code

mcptask_runner init --cli claude
Skills
12 in .claude/skills
MCP config
.mcp.json
Instructions
CLAUDE.md
Models
opus / sonnet / haiku aliases
Approvals
.claude/settings.local.json
terminal

Codex CLI

mcptask_runner init --cli codex
Skills
9 in .agents/skills
MCP config
.codex/config.toml
Instructions
AGENTS.md pointer
Models
gpt-6-astra, gpt-5.6-terra, gpt-5.6-luna
Preflight
codex login status must pass
code

OpenCode

mcptask_runner init --cli opencode
Skills
9 in .claude/skills
MCP config
opencode.json
Instructions
CLAUDE.md
Models
provider/model
Approvals
permission map injected by the runner

report_problemThere is no default

A project that never named a CLI is refused by name, not quietly assumed to be Claude Code. launcher.command stays an override for the argv itself and is cross-checked against harness: — it cannot smuggle in a different CLI than the profile the project declared.

hubWhat is the same on all three

The profile holds the binary, the argv, the resume flag, the failure literals, the tool and skill names, and what init writes. Everything that makes the runner a runner is above it and does not change with the CLI.

enginewatchdogwork loopdashboard cardbug piecerun log

ruleThe limits, stated plainly

call_split

Fork skills are Claude Code only

discover, memory-search and mcptask-read fork a cheap subagent so raw output never reaches the parent context. Only Claude Code can do that, so the Codex and OpenCode profiles ship nine skills instead of twelve. The nine helper scripts are the same on all three.

login

Codex needs a working login

codex login status has to pass before a run starts. The preflight refuses rather than spending a quota on a CLI that will fail on its first call.

lock_open

OpenCode has no read-only flag

There is nothing to pass, so the runner injects a permission map instead. The read-only guarantee is the same; the mechanism is not.

verifiedVerified, not assumed

The conformance and chaos suites run against all three dialects in CI, and the profiles are not a paper feature: a real Codex run finished a task on 2026-09-09, and OpenCode has real runs of its own behind it.

storageYour real environment

On your machine, against reality

Cloud coding agents run in sanitized sandboxes. The runner runs in your actual project.

storage

Real database

It talks to the database your project already uses — the schema, the seed data, the migration history that shaped both. (PostgreSQL is one worked example; whatever your project runs is what the runner drives.)

fact_check

Real system tests

Your project's own test entry points run, end to end — Capybara/Selenium against your real app as one worked example, with the screenshots and browser checks that prove the change actually shipped.

code

Real commits and PRs

Branches, commits, and pull requests live in your repo, with your CI, under your Git history — on GitHub, on Bitbucket Cloud or on GitLab, all opened through `mcptask_runner pr`. Whatever your `bin/ci` runs is what the runner runs.

merge_typePull requests

Pull requests on GitHub, Bitbucket or GitLab

The runner works with a repository on GitHub, on Bitbucket Cloud or on GitLab (gitlab.com and self-managed), and one command opens the pull request on any of them: mcptask_runner pr. No prompt and no bundled skill names a host CLI any more — the binary asks the host, so the same instructions work whichever host your repository lives on.

terminalNothing but mcptask_runner pr opens a PR

It is the fifth public subcommand. Every subcommand prints one JSON object on stdout, errors go to stderr with a non-zero exit, and --git-host overrides the host for a single invocation. git_host: itself is optional — the runner derives it from git remote get-url origin, and init --git-host bitbucket|gitlab is there for aliases, mirrors and self-managed instances.

list_altSix subcommands

pr create

Open the pull request for the current branch.

pr list

List pull requests, filtered by --task, --branch, --open or --state.

pr view

Read one pull request as JSON.

pr reviews

Read the reviews left on a pull request.

pr merge

Merge it — --squash and --delete-branch are the defaults, because that is what auto-squash means.

pr checks

Ask the host what its checks say about the head commit.

keyThree hosts, three credential models

hub

GitHub

Authenticated through gh and its own login. The pull-request template is read from .github/pull_request_template.md, which is a GitHub-only path.

cloud

Bitbucket Cloud

REST API 2.0, with one of two credentials and never both: a BITBUCKET_ACCESS_TOKEN for a machine (Bearer), or BITBUCKET_EMAIL plus BITBUCKET_API_TOKEN for a person (Basic). App passwords are refused — Atlassian ended them on 2026-06-09. init writes ~/.mcptask_env.d/bitbucket_credentials at mode 0600 (the permission check is skipped on Windows), never overwrites an existing value with nothing, and never prints the credential. Outside GitHub the template is declared as pr_template: path:.

account_tree

GitLab

Driven by the glab CLI, which holds its own login — glab auth login, and the runner stores no GitLab credential of its own. gitlab.com and self-managed instances both work: a self-managed project declares git_host: gitlab (mcptask_runner init --git-host gitlab), because its origin URL is not gitlab.com, and the runner never takes a GitLab URL — glab already knows the instance it is logged in to. What pr create opens there is a merge request.

fact_checkAuto-squash asks the host before it merges

A merge is not a hope that CI was green. The runner reads the host's checks for the head commit first: FAILED ends the run as ci_failed with the PR left open, IN_PROGRESS is retried ten times two minutes apart, and NONE — a repository with no host checks at all — falls back to the local gate. GitLab reports one pipeline status per merge request, so the checks view there is a single row, not a list.

report_problemGitHub Enterprise is not GitHub, and is refused by name

A host is matched by its exact hostname, so a GitHub Enterprise instance is rejected with a message that says so rather than being treated as github.com. A PR opened against the wrong host API is worse than a run that refuses to start.

info

Where the runner stops and mcptask.online begins

This is the runner working with a repository on Bitbucket Cloud — not a Bitbucket integration at the mcptask.online level. Pairing commits and pull requests back to pieces runs on webhooks, and those stay GitHub and GitLab.

loopLoop + goal

Loop pulls tasks; goal finishes them

mcptask_runner pairs a work loop (it eats one task after another from the queue) with a goal (it finishes each task to a green PR, not just 'made some edits'). Pick the mode that matches how long you want it to run:

Loop shapeModeAuto-squash vs manualRequired flag
play_circleRun one executor and stoponcemanual
once_drymanual
once_auto_squashauto-squash
reviewmanual
repeatIterate until a stop statustodaymanual
today_auto_squashauto-squash
queue_manual
aliases: queue
manual
queue_auto_squashauto-squash
workflowmanual
reviewsmanual
account_treeWalk a story's subtasksstory_manual
aliases: story
manual--story-id
story_auto_squashauto-squash--story-id
task_manual
aliases: task
manual--task-id
task_auto_squashauto-squash--task-id
todayDo that all day, every daydailymanual

infoA few modes behave differently from the shape they sit in

  • boltonce_dry is the only mode that skips triage entirely — it just shows you the next task and exits.
  • layersworkflow has two phases: reviews first (a PR blocks a human), then today's tasks.
  • scheduledaily never returns on its own — only a crash, a signal, or a failed handover ends the loop. On the way into its overnight sleep it may also hand itself over to a newer binary already on disk (see self-update below); the loop that wakes up tomorrow is the same loop, just running newer code.
  • all_inclusivequeue_manual and queue_auto_squash have no time-of-day gate; they run as long as tasks keep arriving.
buildKey features

What makes it autonomous

Below is the technical detail behind the claim that the runner is 'more than a launcher'. Each feature exists because a real-world run would fail without it.

bug_report

Hitting a foreign bug spawns a tracked task and pauses

If the runner hits an URGENT bug that has nothing to do with its current task, it commits and pushes the in-progress work to the feature branch, switches to main (clean tree), files a new urgent bug task, and returns its id and name. The runner pins the bug to tmp/mcptask_runner/urgent_pin.txt under the project directory (the pin survives a crash and cannot collide between projects), so even after a restart it fixes the bug first and only then returns to the original task. Chaos from 'I hit something unrelated' becomes a tracked, owned item.

restore

Resume after context overflow or interruption

If a run ends because of context overflow, a quota hit, or an urgent bug, the task stays in_progress and the unfinished work stays on the feature branch. The next run picks it up: reads git log + branch state, fetches and merges origin/main, skips already-done steps, and escalates the model to the strongest configured tier so a stronger model can finish it. Long tasks survive across runs. See how an overflow is detected and what context pressure the runner measures in the overflow section.

visibility

Watchdogs and stall detection

The inactivity watchdog kills a run after 20 minutes with no progress, soft-warns on a frozen process after 3 minutes, and applies a tool-specific cap on stuck commands; the absolute silence backstop sits at 50 minutes, with a 45-minute tool-floor that adapts upward from recorded durations up to a 90-minute ceiling. The stall detector reads the CLI's stream line by line — decoded per harness, so the same detector works on Claude Code, Codex CLI and OpenCode — and recognises 'spinning in place' — repeated Edit failures, repeated Bash failures, or an unchanged tool signature across loops; long-running helpers such as CI wait and test wait are explicitly exempt. On a stall the process is killed; the task stays in_progress and resumes on the strongest tier next time. See the quota section for the daily-budget gates and the run log in the observability section for what really happened.

psychology

Triage and model selection (three roles)

Before any work, triage rates the task's complexity and recommends a model role. Three roles: the strongest tier (heavy code), the middle tier (triage, review), and the fastest tier (read only). The runner ships with defaults on an Anthropic host — the CLI resolves the generic aliases at run time, so the concrete model IDs can change without rewriting the retry strings. Codex CLI and OpenCode map the same three roles onto their own ids — gpt-6-astra / gpt-5.6-terra / gpt-5.6-luna and provider/model respectively — so triage does not change with the CLI.

verified

Quality: tests, CI, auto-merge

The full workflow runs end to end: branch → code → unit tests → screenshots → system tests → push → `mcptask_runner pr create` → local CI → `mcptask_runner pr merge`. A task is not done until CI is green, and the merge asks the host's checks first. In auto-squash modes the runner merges the PR itself once CI goes green; in manual modes the PR stays open for human review. The runner can also process review feedback on an open PR. See the loop and goal section for the full mode table.

monetization_onQuota gates

One number, three places to check it

The runner never trusts its own guess. Every quota decision reads a live number from mcptask.online over REST, then enforces it before, between, and during tasks.

sourceWhere the number comes from

The daily budget is fetched live from mcptask.online over REST (GET /api/{account}/users/current/time_status). The runner never asks the agent to estimate its own budget and never reads a cached value — the account is the single source of truth.

gpp_goodThree gates, in this order

WhenWhat gets checked
PRE-RUN (after triage)Re-polls the live REST endpoint after triage. Triage itself takes minutes; a fresh pull catches anything the user spent while the runner was classifying the task.
BETWEEN-TASK (decider)Stops the loop on any failed task, any mid-task quota kill, or a spent daily budget. No third chance at the same day.
MID-TASK (every 360 s)Polls every DefaultQuotaPollInterval. A crossing kills the child and ends the loop with quota_exceeded_mid_task — no retry. The kill streak (DefaultQuotaFailureKillStreak = 3) absorbs about an 18-minute REST outage before the runner gives up.

tuneA deliberate fail-closed / fail-open split

The two questions are not answered the same way on purpose. "Is the quota exceeded?" fails CLOSED; "Can I work today?" fails OPEN. One error blocks a run; the other lets it start.

blockFail closed

When the question is "have we already spent today's budget?" and the REST call cannot answer it, the answer is NO. Better to skip a task than to overspend.

check_circleFail open

When the question is "is there budget left for me to work today?" and the REST call cannot answer it, the answer is YES. Better to start a run than to waste a working day on a transient outage.

reportOutage vs spent budget

A REST outage that killed healthy work is a different bug from an exhausted daily budget. The runner tells them apart on purpose (ErrQuotaPollOutage) — one is nobody's fault, the other is a 20-minute incident worth filing.

ScenarioWhat the runner does
Daily budget spentBetween-task decider stops the loop. No bug filed.
REST poll outage (under kill streak)Retry on the next interval. Loop continues.
REST poll outage (over kill streak, ~18 min)Ends the loop fail-closed with status error and its own termination quota_poll_outage — not quota_exceeded_mid_task. That termination is not on the soft list, so a bug task is filed unconditionally and the twenty-minute incident gets tracked.

--ignore-quota skips all three gates. The runner never queries REST and never refuses a task; the operator takes responsibility for the spend.

memoryContext overflow

What survives when the context overflows

The session is unrecoverable. The work is not — because the work is a branch and commits on disk. Two independent mechanisms carry the rest forward, and a third verdict tells the harness to stop filing noise.

check_circle

Survives

What the runner hands to the next attempt

  • account_tree

    The git branch and its commits

    The work itself is a branch with commits on disk. Even if every byte of the session context is gone, the diff is still reviewable, the PR is still openable, and the changes are still recoverable.

  • history

    Last 3 actions (in-process restart)

    When the budget is fresh and the process restarts in place, the restart preamble carries the last three actions (RecentActionsCap = 3) plus the measured context-cost findings, so the new attempt resumes with momentum instead of cold.

  • rule

    ContextBudget rulebook

    Every restart inherits the shared handoff.ContextBudget() rules — what counts toward the limit, what does not, and how the runner decides the next attempt is safe to start.

block

Lost

What no mechanism can save

  • chat_bubble_outline

    The full conversation context

    --continue would reload the same oversized context, so the SESSION is unrecoverable. Only the last three actions ride along in the restart preamble; everything before that is gone.

layersTwo independent mechanisms

MechanismScopeWhat carries forward
In-process fresh restartInside the same runner process (retry.go, handleContextOverflow)Budget = 1 restart per process (maxOverflowRestarts = 1, deliberate — reviewed in task #11474, kept at 1 in commit f6bce1e). The new attempt reads the last 3 actions, the cost findings, and the budget rulebook.
Cross-process TaskHandoffinternal/handoff/ — one note per task (engine.go, recordTaskHandoff)Writes log/handoffs/task_<id>.json on BOTH the restart and the terminal branch (terminal ends THIS process, not the task). The next NEW process reads it as the prompt preamble for the first attempt — never on a --continue retry. overflow_count accumulates ACROSS runner processes. Notes retire the moment a run ends any other way, and are pruned after 30 days.

gavelThe third verdict: overflow_pr_open

If the restart budget is spent AND an open PR for the task exists, the status is overflow_pr_open, NOT an error. The PR is the deliverable; an open PR with green checks means the work shipped. Filing an error here costs the whole day's loop on a task that is already done (task #11466 lost 79a6f6aa + PR #1659 + the rest of the day over exactly this misclassification, which on top of that auto-filed bug piece #11467 it had not earned).

overflow_pr_open (not an error)error (what it used to be)
restart budget spent + PR open → overflow_pr_openrestart budget spent + no PR → error
memoryFor developers

Why this isn't magic

Four concerns run in parallel: a child in its own process group, a stream reader, a watchdog, and the event stream. The state machine underneath has ten named states and an explicit allow-list - a rejected transition is a warning, never a reason to stop.

lan

Child in its own process group

The runner spawns the coding CLI in a new process group (syscall.SysProcAttr{Setpgid: true}, internal/executor/process_unix.go, configureProcessGroup), so SIGTERM and SIGKILL from the runner hit the whole tree and never leak to the parent. Reader joins are bounded at 30 s (engine.go, stderrJoinTimeout + defaultStdoutJoinTimeout) so a wedged child cannot block shutdown.

stream

Stream reader

Reads the CLI's stream-json line by line as it is produced, in whichever dialect the harness profile declares. Parses events, hunts for the TASKRUNNER_RESULT completion contract, and feeds the StallDetector. No waiting for the process to end — it reacts incrementally to every line that lands.

timer

Watchdog

Independent guard thread on a 30-second heartbeat. Idle kill at 20 minutes, frozen soft-warn at 3 minutes, per-tool hang ceilings (quick / long), an absolute backstop, and a live REST quota poll every 6 minutes. The only outside supervisor over the child.

wifi

Event stream

Persistent WebSocket to mcptask.online over ActionCable (RunnerSessionChannel). Snapshots throttled to ~0.5 s; FSM state changes force-flush. Async reconnect with a 30-second throttle on drop, and a 0.5-second grace window on the final 'closed' frame so the live card never freezes on a stale state. MCPTASK_RUNNER_DISABLE is not a stream-only opt-out — it is the global kill switch for all mcptask.online traffic: the event stream, the quota polls and the bug reports alike (internal/mcptask/endpoint.go, DisableEnv).

hubThe state machine underneath

schema version 3

Ten named states. Every transition is on the allow-list; anything else logs a warning and keeps working. Frozen, pending, stalled and closed are always-allowed transition targets from any state; they are forced by the watchdog (frozen, pending), the stall detector (stalled) and the loop's session close (closed).

States

startingtriageprocessingwaitingfinishedstalledfrozenpendingerrorclosed

Allow-list (a sample)

A few of the transitions the loop uses every minute — and the forced ones the watchdogs fire on their own.

fromtobynote
startingtriageloopAfter spawn, before the first task is picked
triageprocessingloopTask chosen, context delivered to the CLI
processingwaitingloopBacking off between attempts of the same task
processingstalledstall detectorRepeated Edit failures, repeated identical Bash failures, or the same tool signature within the sliding window
anyfrozenwatchdogIdle past FROZEN_WARN_THRESHOLD, no active tools
anypendingwatchdogSingle tool past its warn ceiling
anyclosedloopend_session — the final frame
visibilityObservability

What you can see while a run is in flight

Three independent surfaces — the on-disk run log, the process-wide operational log, and the live ActionCable card — plus the explicit opt-out variables. They are best-effort by construction: watching a run — snapshots and logs — never aborts it.

data_object

Run log — one JSON file per child execution

The most useful surface when the question is "why has the runner been stuck for ninety minutes". The file is opened IMMEDIATELY when the child is spawned, so even an instant crash leaves something to read.

folderWhere
log/runs/run_*.json (one file per child execution)
  • lock_open

    Opened

    At spawn — before the first line of stream-json is even read

  • favorite

    Heartbeat

    Refreshes stream_events, inactive_s and stream_quiet_s every tick, so a hung run leaves its in-flight state on disk

  • task_alt

    Finalize

    Stamps why the attempt ended

Turn "why has the runner been stuck for 90 minutes" from a grep over a 268 000-line stream log into a single file read.

description

Process-wide operational log — broader than the run log

Not a human-formatted mirror of the JSON run log. It is a chronological text log of the whole process: every subsystem writes its Debug/Info/Warn/Error calls into it, so it holds things no run log ever shows — and it does not carry the run log's fields such as session_id or stream_events.

folderWhere
log/mcptask_runner_YYYYMMDD_HHMMSS.log
short_textLine shape
[timestamp] SEVERITY - message (internal/observe/logger.go)
wifi

Live card — one ActionCable WebSocket

One persistent WebSocket carries whole snapshots, never individual events. A dropped frame costs freshness, not correctness, and a reconnect needs no replay.

ChannelPayloadThrottleTimeouts
RunnerSessionChannel (internal/eventstream/eventstream.go, channelIdentifier)Whole snapshots only — no event replay500 ms snapshot, 30 s reconnect throttle, 500 ms final-frame grace10 s subscribe + dial

Finished card: Lingers 60 s after the loop ends (snapshotCloseTTL)

No token: A runner started without a token says so once, loudly, at startup — never silently misses the first event

shield

The boundary — and the opt-outs

Every one of these is best-effort by construction. Outbound observation never aborts a run; a nil *Log is a working no-op. Two environment variables turn the stores off cleanly.

Run logTask handoff
MCPTASK_RUN_LOG=0 — Turns the JSON run log offMCPTASK_TASK_HANDOFF=0 — Turns the cross-process task-handoff note off

The one exception — reassignment: RunnerSessionChannel is duplex: alongside the outbound snapshots it receives the inbound control events piece_assigned and set_aside_cleared (internal/eventstream/eventstream.go). A reassignment is wired through abandonReassigned (internal/runner/loop.go) into the engine's AbandonReassigned — it kills the in-flight child and reclassifies its death as a reassignment. That is the channel's one deliberate control role.

What this does NOT mean: None of the observability surfaces — the run log, the operational log, the outbound snapshots — can stop a run, restart a run, or classify a run. The best-effort property is the precise boundary of the project's no-fallbacks premise: the one place it does degrade, and says so out loud.

Runner fleet: Every runner instance of the account is also listed on its Runner fleet page (user menu): one row per machine and project with the CLI, model, state, last reported task, today's hours against the daily limit and the runner version, updated live. Account managers and company owners see every runner; project members see the runners of their own projects.

bug_reportBug reporting

When a hard failure happens, a bug piece files itself

There is no manual command to run. When a hard failure happens, the runner opens a bug piece for it automatically. A real crash becomes a task somebody can pick up — not a silent gap nobody knows to go looking for.

rule

When a bug piece gets filed

Only status error, status crash or status anomaly counts as a hard failure. Those are the three values in hardStatuses (internal/bugreport/runner_error.go). Every other outcome — success, stalled_for_genius, urgent_bug_pending, or a graceful quota bail — is the runner working as designed and files nothing.

check_circle

Filed

status error, status crash, status anomaly

What an anomaly is

a guard of the runner's own that did not hold in a run which then carried on — nothing failed, the day's work continued, and from the outside there is nothing to see. An error announces itself by ending a run and a crash by killing a process; an anomaly announces itself to nobody, which is exactly why it gets a piece.

horizontal_rule

NOT filed

success, stalled_for_genius, urgent_bug_pending, graceful quota bail — these are working-as-designed verdicts

priority_high

Soft exception

a mid-task quota kill, a spent rate-limit window, or a piece taken away from the runner all arrive wearing status error and are never filed (softTerminations = {"quota", "rate_limited", "reassigned"}, internal/bugreport/runner_error.go)

attach_file

What the bug piece carries with it

Three artefacts are attached to the filed piece (internal/bugreport/runner_error.go, attachArtifacts), so the next person can read the failure without re-running it.

description

Failing attempt's run log

the JSON the on-disk run log finalized with

terminal

Stream log tail

the newest stream log, capped at 512 KB (internal/bugreport/runner_error.go, streamTailBytes)

visibility_off

Redacted config snapshots

.mcp.json, .claude/settings.json, .claude/settings.local.json — tokens stripped (mcptask/piece.go, ConfigFiles + Redact)

fingerprint

One incident is one piece

Identical failures are fingerprinted, throttled, and reported exactly once. Without that, a tight loop would file the same bug 200 times before somebody noticed.

FingerprintNormalizationThrottle windowStamp claim before file
sha256 over termination + normalized message + task id + project, truncated to 16 hex chars (internal/bugreport/runner_error.go, fingerprint)strips paths, hex strings, and decimal numbers — what looks like the same crash with a different timestamp counts as the same crashsix hours (internal/bugreport/runner_error.go, throttleWindow + claim) — the second identical fingerprint within the window is droppedthe fingerprint stamp is claimed BEFORE the piece is created and RELEASED if the piece never gets created (commit 3ae0d6e). The failure most likely to break CreatePiece is the one a retry would otherwise silently suppress
warning

Panics — re-raised, with a stack trace

A panic is a hard failure with a forensic trail. The piece gets the trace; the loop keeps the panic (loop.go, reportPanic, commit 1681e79). Otherwise the trace would be eaten by the panic recovery and lost to whoever picks up the task next.

shield

Fail-safe throughout

Every reporting path swallows its own errors, including its own panic (internal/bugreport/runner_error.go, MaybeReport defer/recover). A reporting failure can never end a run. The whole premise is that the worst-case reporter is the one that costs the least — a swallowed log line, not a missed crash.

visibility_off

The blind spot, stated openly

The reporter authenticates with the same token as everything else, so it CANNOT file the bug that says the token is missing. A missing or unconfigured token skips the reporter silently — OutcomeSkippedDisabled writes no error line, so only the runner's own exit code and log carry it. A token the server refuses is the loud path instead: OutcomeRefused logs an error naming the token (internal/bugreport/runner_error.go). The alternative was to hide the gap; the choice here is to name it.

swap_horizFor developers

Model-agnostic by configuration

Three tiers map to any provider and to any of the three coding CLIs. Point the runner at any Anthropic-API-compatible endpoint — local Ollama is the documented example — by changing the launcher and the models section.

The models: section is optional. Without it each harness uses its own generic aliases, resolved by that CLI at runtime: opus / sonnet / haiku on Claude Code, gpt-6-astra / gpt-5.6-terra / gpt-5.6-luna on Codex CLI, and provider/model ids on OpenCode. Pin versioned IDs only when you want deterministic retries, or when routing through a non-Anthropic backend — and once you do, it is ALL OR NOTHING: all three of genius / smart / primitive must be set, or forked subagents fail with "model may not exist".

terminalPer-harness defaults
# models: is optional. Without it each harness falls back to the generic
# aliases its own CLI resolves at run time.

# Claude Code
models:
  genius:    opus
  smart:     sonnet
  primitive: haiku

# Codex CLI
models:
  genius:    gpt-6-astra
  smart:     gpt-5.6-terra
  primitive: gpt-5.6-luna

# OpenCode — every id is provider/model
models:
  genius:    /
  smart:     /
  primitive: /
terminalExample: non-Anthropic backend
models:
  genius:    minimax-m3:cloud
  smart:     kimi-k2.7-code:cloud
  primitive: deepseek-v4-flash:cloud

launcher:
  # Local ollama
  command: [env, "ANTHROPIC_BASE_URL=http://localhost:11434", "ANTHROPIC_AUTH_TOKEN=ollama", "claude"]

The IDs below are one example, not a recommendation — they age fastest of anything on this page. The shape is what matters.

launcher.command flags (launcher.go, Flag* constants): a null flag is OMITTED from the spawned argv, a value flag with no token is passed POSITIONALLY.

auto_fix_highThe loader never fails

A missing, empty, or malformed config/mcptask_runner.yml is treated as "no config" (config.go, LoadFile) and the built-in defaults apply. There is no "file not found" exit code — the runner starts either way.

check_circleNo pin

No shipped skill declares `model:` — ForkModelEnv therefore reaches every forked subagent, including ones running on a non-Anthropic launcher.

info

The configuration lives in config/mcptask_runner.yml, resolved relative to the working directory of the launcher (config.go, FileName; README.md) — the same file the Ruby gem reads, with the same semantics. You can also set additional options there, such as the destination Epic for auto-found bugs or the waiting strategy between runs.

GEM — exact file path, exact semantics

system_update_altSelf-update

update --self, and the deliberate non-feature

The runner is a static binary installed by download, not a gem that bumps on every bundle. Two commands move it: update --self fetches a newer release and swaps it in, update --check reports whether one exists and does nothing else. The third decision — whether the runner should ever move itself before a scheduled run — is the one we deliberately do not automate.

swap_horiz

How update --self swaps the binary

The new binary lands in the same directory as the old one and a rename dance moves it into place (selfupdate.go). That sequence matters more than it looks: a rename is only atomic within ONE filesystem, and /tmp usually is not the same one, so the new file is staged alongside, not copied over from somewhere else. A failed or mismatched download never reaches the swap — the binary on disk is untouched and the command exits non-zero.

file_download

1. Fetch from the public releases mirror

the binary for this GOOS/GOARCH is downloaded from jchsoft/mcptask-releases (selfupdate.go, DefaultRepo). The source repo is private, so the public mirror is the only host the download can come from without a token

verified

2. Verify against the release's checksums.txt

the asset's sha256 is compared to the one the release published (selfupdate.go, verify). A mismatch aborts before any write touches the install directory

file_copy

3. Stage alongside, rename the running binary aside

the new binary is written next to the running one, then the running one is renamed to a sibling name. That is also what makes Windows work — overwriting a file that is mapped for execution fails on Windows, and the rename-aside step sidesteps that

published_with_changes

4. Rename the staged binary in, atomically

a final os.Rename on the same filesystem moves the staged binary into the install path. Atomic within that filesystem. The command exits 0 only after the swap

visibility

update --check — a tag lookup, no download

update --check resolves the latest release tag, compares it to the running version, reports the answer and exits. It downloads no asset and verifies no checksum — none of the four steps above run. The binary on disk is not touched. It is the right command to wire into a nightly job that wants to alert without installing.

shield

The deliberate non-feature

The runner does NOT update itself before a scheduled run. It prints a one-line hint when a newer release exists and does nothing else, because one bad release must not reach every unattended host at 08:05 simultaneously. Upgrading stays the operator's decision — a trust claim worth making explicitly to anyone being asked to leave a binary running unattended.

notifications

What the hint does

selfupdate.Hint prints one line when a newer release exists (hint.go, hintTimeout + hintInterval). Capped at 2 s, cached for 24 h, ignoring every error — a network blip or a rate-limit never reaches the run

block

How to turn it off

set MCPTASK_NO_UPDATE_CHECK=1 in the host environment. That is what an air-gapped or metered host wants, and the check is silenced before the cache file is even read

inventory_2

One of two paths that do re-exec — bundle adoption

BUNDLE ADOPTION (commit 00ced53, task #11478): on run only, if the project's own Gemfile.lock names a newer wrapper-gem version than the running binary, the run re-execs on that binary. It FETCHES NOTHING — the operator already made that decision by merging the lockfile. The 08:05 host reads the lock it pulled from main and runs what the lock says to run.

repeat

MCPTASK_ADOPTED

set in the environment of the re-exec'd child, this stops adoption from becoming an exec loop (adopt.go, EnvMarker). A binary that reports older than the lock would otherwise adopt, exec, find itself outdated again, and spin

push_pin

MCPTASK_NO_ADOPT

set to 1 to pin the installed binary no matter what projects carry. A host that wants one runner version across every project it runs sets this once and forgets it

bedtime

The other path — daily hands itself over on the way to bed

On the way into its overnight sleep, a daily run checks whether the binary on disk is still the one it started from. If not, it releases the instance lock and execs the installed binary — restartOnCurrentBinary (restart.go). That handover DOWNLOADS NOTHING either — it only adopts what update --self or an operator already put on disk. The ordering is load-bearing: the day's work is finished, so no child is alive and no working tree is half-written, and the process that wakes up tomorrow is the one that started this evening.

help_outline

Why it exists

everything that notices a new version happens once, at startup. A daily process starts once and never ends, so an unattended host would otherwise keep whichever binary it happened to start with however many releases go by — exactly the host nobody is watching. Making daily exit instead was considered and rejected (task #11728): a runner that goes away is one a missing or unloaded scheduled job never brings back

lock_open

ErrLockLost — the third ending

restart.go declares ErrLockLost, a handover that let go of the instance lock and then could not take it back. That ending DOES stop the loop, deliberately and loudly — carrying on unlocked would let the next scheduled start put a second runner in the same checkout

autorenewMaintenance

Skill Updates Without Losing Your Edits

An installed project drifts as the runner gains skills. One command re-syncs it: mcptask_runner update. It compares every skill and helper against the install manifest, reports what it did to each one, and refuses to overwrite anything you edited yourself. It is also how an installed project stops calling `gh`: the bundled skills migrated to `mcptask_runner pr`, and `update` is what brings that migration to a project installed before it.

The command

mcptask_runner update
fact_check

Five outcomes, one per file

The updater holds a content hash for every file it installed. Each skill and helper is classified against that manifest and the result is printed — five outcomes, not a silent copy. A project installed before the PR work reads its skills as `updated` — they are the shipped version, untouched by you — so the `gh` calls inside them are replaced without asking.

add_circle_outlineadded

The file is not on disk. The updater writes it. New runner capabilities land this way without you asking for them by name.

check_circle_outlineup-to-date

The file on disk hashes to the manifest entry. Nothing is written. A gem-installed host reads as up-to-date to this binary — checked directly against the file, not assumed from the install channel.

syncupdated

The file matches an older manifest entry, so it is the shipped version and you never touched it. Safe to replace, and it is replaced.

blockconflict-skipped

The file hashes to neither the current nor a known shipped version — you edited it. The updater leaves it exactly as it is and says so. This is the default, and it is why the command is safe to run on a project you have customised.

restore_pageforce-updated

The same conflict, resolved the other way because you asked. Only reachable with --force or FORCE=1.

restore_page

Overwriting a conflict, with a way back

mcptask_runner update --force

--force (or FORCE=1) overwrites locally modified files instead of skipping them — and keeps a .bak next to each one it overwrites. Your edits are not deleted; they are moved aside where you can diff them.

shield

What plain update deliberately leaves alone

update touches ONLY skills and helpers. Everything else the installer once wrote stays exactly where it is. That restraint is the selling point, not an omission: re-syncing skills must not silently re-activate a schedule or rewrite a config file you tuned by hand. `git_host:` and `harness:` are config, so `update` never touches them either — moving a project to another PR host or another coding CLI stays an `init` decision.

schedule

The scheduled job

The LaunchAgent, systemd user timer or Task Scheduler job is not regenerated and not re-enabled. Use init --schedule when you actually mean to regenerate it.

data_object

.mcp.json

Your MCP client configuration is not reopened, not rewritten and not downgraded. The transport and the token variable you declared survive the update untouched.

tune

The config sections

config/mcptask_runner.yml keeps the values you set — the work window, the launcher command, the end-of-workday hour. update has no opinion about them.

system_update_alt

Updating the binary is a separate decision

update re-syncs the project. It does not move the binary that did the syncing. Two other commands do that, and neither of them ever runs on its own: mcptask_runner update --self swaps in a newer release, and mcptask_runner update --check reports whether one exists and changes nothing.

person

The runner never updates itself before a run

At the start of a run the runner may print one line saying a newer release exists. That is the whole feature — capped at 2 s, cached for 24 h, every error ignored, and silenced entirely by MCPTASK_NO_UPDATE_CHECK=1. Upgrading stays the operator's decision, because a runner that moved itself before a scheduled run would be changing the thing doing the work without being asked. That matters if you are leaving a binary running unattended.

verified

A bad download never reaches the swap

update --self downloads from the public mirror jchsoft/mcptask-releases and verifies the asset against the release's checksums.txt. A failed or mismatched download aborts before any write touches the install directory: the binary on disk is untouched and the command exits non-zero.

The full swap sequence — and why the rename dance is what makes Windows work — belongs to the Runner page.

system_update_altRead the self-update mechanics
list_altCLI reference

Five public subcommands, two persistent flags, three exit codes

The runner is a single binary on a cobra root: run, init, update, version, pr. A sixth subcommand, internal, is hidden from --help because generated launcher scripts call it, not operators: internal launcher-log-name is how the Windows launcher names its own log. Hidden is not provisional — the launcher depends on it at run time. Two persistent flags --verbose and --ignore-quota are read case-insensitively from the matching env var (verbose=true, ignore_quota=true). Per-subcommand flags are listed below each command. Exit code is 0 on success, 3 for an unimplemented mode, 1 for anything else, and 128+N on a signal. 3 is a designed sentinel rather than an ending you can reach today: every mode is implemented, so nothing returns ErrModeNotImplemented.

terminalSubcommands

run

Run a work-loop mode. The mode is required; aliases queue, story and task expand to queue_manual, story_manual and task_manual.

init

Install on this host. Writes skills, helpers, permissions, token, the .mcp.json entry, the config sections and the scheduled job.

update

Refresh bundled skills and helpers. --self swaps the binary in place; --check reports without installing.

version

Print the banner (version and resolved configuration) and exit.

pr

Open, list, read, review, merge or check the pull request on GitHub, Bitbucket Cloud or GitLab. One JSON object on stdout per invocation.

tune

Persistent flags

These two flags live on the root command and apply to every subcommand. Each has an equivalent env var read case-insensitively for the literal string "true".

flag--verbose

verbose=true

Dump every stream line instead of the filtered view.

flag--ignore-quota

ignore_quota=true

Skip every quota check.

play_circle

run — execute a work-loop mode

Picks the mode and runs it. The mode is required; queue, story and task are aliases for queue_manual, story_manual and task_manual. --story-id is required by story_manual and story_auto_squash; --task-id is required by task_manual and task_auto_squash. On start the runner prints a banner with version, models source, launcher source, bug destination, waiting strategy, work window and today's skip list, followed by the resolved-configuration summary, then the bundle-adoption check, the update hint, the token preflight, the single-instance lock and the signal handler — in that order — before the loop itself begins.

input--story-id N

-

Required by story_manual and story_auto_squash; rejected without one.

input--task-id N

-

Required by task_manual and task_auto_squash; rejected without one.

download_for_offline

init — install on this host

Writes the bundled skills — twelve for Claude Code, nine for Codex CLI and OpenCode — the .claude/test-commands.json test declaration the bundled test skills read, nine helper scripts (the ninth, runner-log, is the one an operator types at a shell), the baseline permissions, a token exchange, the mcptask-online entry in .mcp.json, the bug-destination block in config/mcptask_runner.yml, the waiting-strategy defaults and the scheduled job. --at and --until pin the run time and the end-of-workday hour; --schedule regenerates the scheduled job alone. init asks two questions interactively — which Epic holds auto-detected bugs, and which mode the scheduled job runs — and --epic-id, --epic-name and --mode answer them without a terminal, because a binary a provisioning script installs has to be installable without one. --cli names the coding CLI this project is driven by and is required the first time, with no default; --git-host pins the PR host when the origin URL cannot say it. --helper-bin-dir and --home-dir re-root what init writes, --force overwrites what is already there. The scheduled job is generated but never activated — turning on a job that spends a quota every morning is the operator's decision.

input--cli NAME

-

Which coding CLI drives this project — claude, codex or opencode. Required on the first init; there is no default, and a project that never named one is refused by name. Written to config/mcptask_runner.yml as harness:.

input--git-host HOST

-

github, bitbucket or gitlab, written as git_host:. Optional — the runner derives it from git remote get-url origin; this is for aliases, mirrors and self-managed GitLab instances, whose origin URL is not gitlab.com. The runner never takes a GitLab URL: glab holds the instance it is logged in to.

input--at HH:MM

MCPTASK_RUN_AT

Time of day the scheduled job runs. Default 08:00. A value that is not a time of day is refused, never rounded.

input--until HH:MM

MCPTASK_WORKDAY_UNTIL

End-of-workday hour written to config under work_window.end_of_workday_hour. Unset means no end of day. Editable later without regenerating the schedule.

input--schedule

-

Regenerate the scheduled job and nothing else — skills, permissions, token and the config sections are left exactly as they are.

input--mode MODE

-

Work-loop mode the scheduled job runs, e.g. today_auto_squash. Given, it answers the mode question init would otherwise ask.

input--epic-id N

-

mcptask.online Epic that holds auto-detected bugs; 0 keeps them at the project root. Given, it answers the Epic question.

input--epic-name NAME

-

Display name recorded alongside --epic-id, so the config says which Epic that number is.

input--helper-bin-dir PATH

-

Where the CI and test helper scripts are installed. Default ~/.claude/bin.

input--home-dir PATH

-

Re-roots the token directory, the shell rc file and the scheduled job. Default is the user's own home.

input--force

FORCE=1

Overwrite what is already on the host — skills, config sections and the scheduled job. Wider than update's --force, which only rewrites modified skills and keeps a .bak.

system_update_alt

update — refresh skills and helpers

A plain update touches only the bundled skills (twelve for Claude Code, nine for Codex CLI and OpenCode) and the nine helper scripts; it deliberately leaves the scheduled job, .mcp.json and the config sections alone. --self fetches the latest release and swaps the binary in place (same filesystem, rename dance); --check reports whether a newer binary exists and changes nothing. --force overwrites locally modified skills, keeping a .bak. Bundle adoption is one of two paths that re-exec, on run only — when the project's own Gemfile.lock names a newer wrapper-gem than the running binary. The other is daily, which hands itself over to a newer binary already on disk on the way into its overnight sleep.

input--self

-

Fetch the latest release, verify against checksums.txt, swap the binary in place. A failed or mismatched download never reaches the swap.

input--check

-

Resolve the latest release tag, compare versions, report and exit. No download, no checksum verification. The binary on disk is not touched.

input--force

FORCE=1

Overwrite locally modified skills, keeping a .bak alongside the rewritten file.

info

version — print banner and resolved configuration

Prints the banner (version + resolved models + launcher source + bug destination + waiting strategy + work window + today's skip list), followed by the resolved-configuration summary block — the same one init prints at the end of an install — and exits. Version comes from an ldflags stamp, falling back to the module version and finally to 0.0.0-dev when the binary was built without one.

merge_type

pr — open, read and merge the pull request

The only thing that opens a pull request. Six subcommands — create, list, view, reviews, merge and checks — each printing one JSON object on stdout; errors go to stderr with a non-zero exit, so a caller can tell the two apart without parsing prose. merge defaults to --squash --delete-branch. The host is GitHub, Bitbucket Cloud or GitLab (gitlab.com and self-managed, driven by the glab CLI), derived from git remote get-url origin unless git_host: says otherwise, and --git-host overrides it for a single invocation. A host is matched by exact hostname, so GitHub Enterprise is refused by name rather than quietly treated as GitHub.

input--git-host HOST

-

Override the PR host for this invocation — github, bitbucket or gitlab. Without it the host comes from git_host: or from the origin URL.

input--task N

-

list only: the pull requests opened for one mcptask.online task.

input--branch NAME

-

list only: the pull requests whose head is this branch.

input--open / --state STATE

-

list only: restrict by state. --open is the shorthand for the open ones.

numbers

Exit codes

code0

Success — including a run whose task ended in {"status":"error"}.

code1

Anything else.

code3

Unimplemented mode (ErrModeNotImplemented) — a designed sentinel; every mode is implemented, so nothing reaches it today.

code128+N

Killed by signal N.

verifiedProof

Conformance scenarios and the CI matrix

The evidence behind every reliability claim on this page. Recovery, watchdog, stall detection and quota are all asserted with no evidence offered anywhere; this section is the receipts.

playlist_play

Fourteen conformance scenarios

Each scenario is a recorded conversation with the claude CLI plus the behaviour a runner is required to show while replaying it. The scenarios are implementation-neutral: plain YAML plus the contract in conformance/README.md, with no Go or Ruby in them. Driven with `go run ./cmd/conformance {list,run --impl go}`.

Every failure mode the runner claims to survive has its own recorded scenario in conformance/scenarios/. The two implementations run the same files; the Ruby gem is the retired, frozen reference implementation the suite can be compared against, and the gate is the Go suite being green.

Scenarios 
01 — successthe happy path: a clean run ends with a green verdict, no retries, and a branch ready to push
02 — missing_marker_retrythe run log never finalises a marker; the runner retries until the budget runs out and then bails with a verdict instead of a hang
03 — context_overflow_fresh_restartcontext overflow while the engine is still running — the in-process restart is what carries the work forward and keeps the run alive
04 — context_overflow_terminalcontext overflow after the engine has stopped — the carry-forward machinery hands the file to a new process and that is the only way the run gets out alive
05 — api_overload_529the upstream returns 529 — the runner backs off, the run stays on the right task, and the marker does not advance on a transient
06 — tool_not_enabled_fresh_restarta tool the run needs is missing on the first attempt; `--continue` re-runs it as a fresh restart and the run recovers
07 — tool_not_enabled_terminalthe same tool is still missing after a fresh restart — the run bails with a verdict, not a hang
08 — stall_edit_failuresthe engine edits a file the harness watches; the edits diverge and the watchdog catches it before the marker advances on a lie
09 — inactivity_kill_then_recoverthe child process stops talking — the watchdog kills it, the debug dump is left on disk, and the next attempt picks up the same task
10 — recoverable_retries_exhaustedevery recoverable retry has been spent and the run is still not done — that is a verdict, and the verdict is the only safe outcome
11 — quota_mid_taskthe budget runs out while the engine is mid-edit — the soft kill lands, the run saves a clean state, and the midnight cap of the next window starts a fresh attempt
12 — stream_closedthe upstream closes the stream mid-turn — the runner detects the close, treats the run as incomplete, and the next attempt re-derives state from disk rather than the gone stream
13 — hung_tool_killa tool the engine called has stopped answering — the watchdog kills the child with the kill-on-hang signal and the run is left re-runnable
14 — triage_unverified_pickthe next-task picker returns a task that has not been verified — the runner refuses to start it, files nothing, and waits for a verified candidate rather than picking something blindly
science

The binary carries no test hook of its own

The runner is pointed at a mock CLI through the ordinary `launcher.command` override (README.md) — the same per-host configuration a developer uses to drive a real one. The suites run in all three harness dialects (Claude Code, Codex CLI, OpenCode), and a conformance project on Bitbucket Cloud and one on gitlab.com cover the other two PR hosts, so a green run is not a green run on one dialect against one host. A technical reader recognises that as a quality signal — there is no test-only code path in the binary for the conformance suite to lean on, so a green run here is the same code a user installs.

grid_view

The CI matrix

Every commit runs a 3-OS x 2-Go-version matrix (ubuntu / macos / windows x 1.25.x / stable), go vet, race-detector tests on Unix + stable, bin/smoke on the BUILT binary, gofmt, golangci-lint, a goreleaser config check plus a six-target cross-compile, and the whole conformance suite (.github/workflows/ci.yml). The matrix exists because the runner is installed on developer machines — every desktop platform has to build and pass.

fact_check

The honest half

A green LOCAL run prints what it did NOT cover — "This machine only: <goos>/<goarch>" — because one OS, one architecture and one Go version is not what CI runs (bin/ci). Green locally is necessary, not sufficient. The Ruby comparison run is a reference check, not a gate: it is skipped, not failed, when the retired gem is not checked out — a machine that cannot reach the reference should still be able to check its own work.

visibility_off

The boundary, named

The conformance harness runs one runner process per scenario, so it cannot express the cross-process half of overflow survival; that is covered by tests running two engines in sequence sharing only the file (README.md). The Ruby comparison run is skipped, not failed, when the retired gem is not checked out, and it holds up no merge either way.

Technical FAQ

Short answers to questions this page raises but does not answer in place. Each answer points at the owning section rather than restating it.

help_outline

Does the runner need Ruby, bundler or a Gemfile?

No. Project metadata comes from CLAUDE.md and config from config/mcptask_runner.yml — neither goes through Ruby. See section 1. No prompt names a framework command either: a project that needs a build step before its browser suite declares it in its own CLAUDE.md.

help_outline

Does it work on a non-Rails project?

Yes — any project under git, on GitHub, on Bitbucket Cloud or on GitLab (gitlab.com and self-managed), with one of the three supported coding CLIs on the host and a CLAUDE.md declaring account_code and project_relative_id. There is no default CLI: init --cli claude|codex|opencode chooses it, launcher.command only overrides the argv and is cross-checked against harness:. See section 2.

help_outline

Can two runners share one checkout?

No. The second runner refuses to start; lock.go (AcquireInstanceLock) enforces one runner per checkout. See section 1.

help_outline

What happens when the daily quota runs out mid-task?

The current attempt exits as a soft termination (status error, never filed as a bug) and the loop waits for the next gate. See section 6.

help_outline

What does a crash leave behind, and who finds out?

A hard failure (status error, status crash or status anomaly — a guard of the runner's own that did not hold in a run which then carried on) files its own bug piece with the run log attached, fingerprinted and throttled for six hours. The reporter cannot file the bug that says its own token is missing — that one surfaces through the exit code and the log. See sections 9 and 10.

help_outline

Does it update itself unattended?

No, deliberately. `update` touches only skills and helpers by default; `update --self` upgrades the binary in the user's terminal, not in a background loop. See section 12.

help_outline

Which OSes does it run on, and what does a green local test run not cover?

macOS (launchd), Linux (systemd user timer) and Windows (Task Scheduler) each have their own scheduled job. A green local test run covers unit and system tests on the host's Go version; it does not cover the three-OS × two-Go-version matrix or the fourteen conformance scenarios that gate a release. See section 14.

rocket_launch

Turn the machine on in the morning

mcptask_runner does not replace your team — it multiplies it. One static binary, one install line, and your backlog moves by itself.

verified_userFree 30-day trial. No credit card. Cancel any time.