name: tusk description: Get the most important task that is ready to be worked on
Tusk Skill
The primary interface for working with tasks from the project task database (via tusk CLI). Use this to get the next task, start working on it, and manage the full development workflow.
Use
/create-taskfor task creation — handles decomposition, deduplication, criteria, and deps. Usetusk task-insertonly for bulk/automated inserts.
Setup: Discover Project Config
Before any operation that needs domain or agent values, run:
tusk config
This returns the full config as JSON (domains, agents, task_types, priorities, complexity, etc.). Use the returned values (not hardcoded ones) when validating or inserting tasks.
Commands
Get Next Task (default - no arguments)
Finds the highest-priority task that is ready to work on (no incomplete dependencies), opens a session for it, flips its status to In Progress, opens a skill-run row for cost tracking, and returns the same JSON blob documented under "Begin Work on a Task" below — all in one call.
tusk task-start --force --skill tusk
The --force flag bypasses the zero-criteria guard only (emits a warning rather than hard-failing). It does not bypass dep blocking or unresolved external blockers — those are separate guards. To bypass an unmet blocks-type dependency, pass --force-deps (use sparingly — blocks deps exist for a reason). The --skill tusk flag opens a skill_runs row attributed to this task; run_id is returned under skill_run.run_id in the JSON — capture it for the cancel/finish calls later.
Empty backlog: If the command exits with code 1, the backlog has no ready tasks. Check why:
tusk -header -column "SELECT status, COUNT(*) as count FROM tasks GROUP BY status"
- If there are no tasks at all (or all are Done): inform the user the backlog is empty and suggest running
/create-taskto add new work. - If there are To Do tasks but all are blocked: inform the user and suggest running
/tusk blockedto see what's holding them up. - If there are In Progress tasks: inform the user and suggest running
/tusk wipto check on active work.
Do not suggest /groom-backlog or /retro when there are no ready tasks — those skills require an active backlog or session history to be useful.
On success, the JSON blob's task.id is the task you just started and skill_run.run_id is the open skill-run row. Immediately proceed to step 1b of the "Begin Work on a Task" workflow — do not wait for additional user confirmation.
Begin Work on a Task (with task ID argument)
When called with a task ID (e.g., /tusk 6), begin the full development workflow. When called with no argument, the "Get Next Task" step above has already run tusk task-start --force --skill tusk for you — skip Step 1 entirely and pick up at Step 1b (Workflow routing), using the JSON blob and the skill_run.run_id you already captured.
Follow these steps IN ORDER:
Start the task and begin cost tracking — fetch details, check progress, create/reuse session, set status, and open the skill-run row in one call:
tusk task-start <id> --force --skill tuskThe
--forceflag bypasses the zero-criteria guard only (emits a warning rather than hard-failing) — it does not bypass dep blocking or unresolved external blockers. If the task has unmetblocks-type dependencies, the call exits 2 with the blocker list; pass--force-depsto bypass that guard with a warning (use sparingly —blocksdeps exist for a reason). The--skill tuskflag opens askill_runsrow so this session's spend can be attributed to the task. This returns a JSON blob with these keys:task— full task row (summary, description, priority, domain, assignee, etc.)progress— array of prior progress checkpoints (most recent first). If non-empty, the first entry'snext_stepstells you exactly where to pick up. Skip steps you've already completed (a task workspace may already be recorded, some commits may already be made). Usegit log --onelinein the task workspace to see what's already been done.criteria— array of acceptance criteria objects (id, criterion, source, is_completed, criterion_type, verification_spec). These are the implementation checklist. Work through them in order during implementation. Mark each criterion done (tusk criteria done <cid>) as you complete it — do not defer this to the end. Non-manual criteria (type: code, test, file) run automated verification ondone; use--skip-verifyif needed. If the array is empty, proceed normally using the description as scope.session_id— the session ID to use for the duration of the workflow (reuses an open session if one exists, otherwise creates a new one)skill_run—{run_id, skill_name, started_at, task_id}for the skill-run row opened by--skill. Captureskill_run.run_id— it's referenced by every exit path below.
Hold onto
session_idfrom the JSON — it will be passed totusk mergein step 12 to close the session. Do not pass it totusk task-done; usetusk mergefor the full finalization sequence.Early-exit cleanup: If any step below causes the skill to stop before reaching the final
/retroinvocation in Step 12, first calltusk skill-run cancel <run_id>to close the open row, then stop. Otherwise the row lingers as(open)intusk skill-run listforever. The explicit cancel calls below cover the known post-start early-exit paths; if you hit an unexpected bail-out, cancel before returning.Pre-start exits don't need cancel. If
tusk task-start --force --skill tuskexits 1 (empty backlog — "No ready tasks found") or exits 2 (task not found, already Done, has unmetblocks-deps without--force-deps, has open external blockers, or missing criteria without--force), the skill-run row is never opened, so there is norun_idto cancel. Just stop.
1b. Workflow routing — If the task's workflow field (from the task object in step 1) is non-null, the task uses a custom workflow instead of the default development cycle. Look up the corresponding skill:
Read file: .agents/skills/<workflow>/SKILL.md
If the file exists, cancel the /tusk skill-run (the handoff skill will open its own run) and stop following the steps below, following that skill's instructions instead, passing the task ID and session_id from step 1:
tusk skill-run cancel <run_id>
If the file does not exist, log a warning ("Workflow '
Create or reuse the task-owned workspace IMMEDIATELY:
tusk task-worktree create <id> <brief-description-slug>This creates a recorded task workspace and feature branch, or returns the existing recorded workspace for the task. Parse the JSON response, then
cdintoworkspace_pathbefore exploring, editing, testing, committing, or merging. Ifcreatedisfalse, continue from that existing workspace; do not create another branch or overlapping worktree. If you are already in the returnedworkspace_path, stay there.For LaughTrack scraper work,
task-worktree createalso linksapps/scraper/.venvfrom the primary checkout into the task workspace when the primary checkout has that venv. This makes acceptance commands such ascd apps/scraper && .venv/bin/python3 -m pytest ...work in task-owned worktrees without manual setup. If the primary checkout has no scraper venv, create it there first withcd apps/scraper && make setup-venv.For LaughTrack web work,
task-worktree createlinks ignored local resources from the primary checkout when they exist:apps/web/node_modules,apps/web/.env.local, andapps/scraper/.env. This makescd apps/web && npm run type-checkandnpm run devwork in task-owned worktrees without manual symlink setup, while leaving any existing local files in the task workspace untouched.If you need to inspect recorded workspaces before deciding where to continue, run:
tusk task-worktree list --format jsonUse the row for this task when present. The recorded workspace is the normal task boundary; do not use
tusk branchfor the default/tuskworkflow.Deliverable check: If
deliverable_check_neededfrom step 1 istrue, run:tusk check-deliverables <id>(Replace
<id>with the actual task ID.) This command checks all branches for commits referencing the task and, if none are found, scans the task description and criteria for referenced file paths and tests whether they exist on disk. Act on therecommendationfield:"commits_found"—[TASK-<id>]commits exist on a non-default branch (typically a stale feature branch from a prior session). Switch to it or cherry-pick the relevant commits before proceeding to Explore."merged_not_closed"—[TASK-<id>]commits already exist on the default branch AND their diff overlaps with files referenced in this task (or there is no scope signal to compare against). Treat as the orphaned-task case: work was merged without being finalized throughtusk merge. The SHAs are listed indefault_branch_commits. Skip implementation entirely. Mark all criteria done with--skip-verify, then jump straight to step 12 to close out the session —tusk mergewill detect the already-merged state and finalize without re-merging."merged_not_closed_low_confidence"—[TASK-<id>]commits exist on the default branch but their diff (listed indefault_branch_commit_files) does NOT overlap with files referenced in this task's description / acceptance criteria / verification specs, NOR with files modified on any[TASK-<id>]commit on a feature branch. This is the prefix-match false-positive case (issue #606, original incident TASK-1691): another task's commit was likely tagged with this task's[TASK-N]prefix by mistake. Verify before acting — inspect each commit listed indefault_branch_commits(git show <sha>) and confirm whether it actually represents this task's work. If yes, treat asmerged_not_closed(skip implementation, jump to step 12). If no, ignore the on-default commits and proceed normally with Explore → Implement as if the recommendation wereimplement_fresh."mark_done"— no commits, but deliverable files listed infilesalready exist on disk. Mark all criteria done with--skip-verifyand proceed directly to step 9 (commit + merge) without reimplementing."criteria_complete_no_commits"— every non-deferred acceptance criterion is already markedis_completed=1, but there are no[TASK-<id>]commits anywhere AND no deliverable files on disk. This is a salvage / converged-work / speculative-mark signal (issue #578, original incident TASK-1714): a prior session marked criteria done without producing any committed deliverable. Common causes: (1) lost-work — a prior agent did real work but couldn't commit cleanly (dirty worktree, branch protection, bundled unrelated changes on a salvage branch); (2) convergent-evolution — separate tasks effectively achieved the goal, so no fresh commits are needed for THIS task; (3) speculative pre-marking — criteria were marked done at the start of a prior session without backing code. Do NOT silently proceed asimplement_fresh. Instead: (a) read the task's progress notes viatusk task-get <id>and inspect anynext_stepsreferences; (b)git branch -a | grep TASK-<id>for stale branches and inspect their diff against the default branch (git log <branch>..origin/<default>andgit show <sha>) to determine whether the work is obsolete vs. still relevant; (c) surface the options to the user — re-implement (proceed with Explore → Implement as ifimplement_fresh), accept-as-converged (close viatusk abandon <id> --reason completed --note "<rationale referencing the converging task or commits>"), or abandon (close viatusk abandon <id> --reason wont_do --note "..."). Do not pick the path unilaterally."implement_fresh"— no commits, no deliverable files found, and at least one non-deferred criterion is still incomplete (or the task has no criteria at all). Proceed normally and implement from scratch.
Determine the best subagent(s) based on:
- Task domain
- Task assignee field (often indicates the right agent type)
- Task description and requirements
Confirm failure — Run the failing tests before exploring any code when the task is about fixing an existing failure. This confirms the bug still exists and avoids wasted investigation.
When to run this step:
task_type: bug→ always runtask_type: testAND the summary/description indicates fixing a failing or flaky test → runtask_type: testAND the summary/description indicates writing new tests (no existing failure to reproduce, e.g. "Add tests for X", "Write test suite for Y") → skip this step entirely and proceed to Explore- All other task types (feature, chore, docs, etc.) → skip
- Check the task description and acceptance criteria for specific test commands or test names to run.
- If specific tests are named, run them directly. Otherwise, use
tusk test-detectto find the project's test command, then run the most relevant subset. - If tests pass: the issue may already be fixed or the description may be inaccurate — run
tusk skill-run cancel <run_id>, surface this to the user, and stop before investigating further. - If tests fail: capture the failure output. Use it as the primary diagnostic anchor in step 5 (Explore).
Explore the codebase before implementing — use a sub-agent to research:
- What files will need to change?
- Are there existing patterns to follow?
- What tests already exist for this area?
- For each file you plan to modify, grep it for keywords related to the feature (e.g., the concept name, the config key, the resource type). If a helper function already exists that covers what you're about to write, use it instead of duplicating the logic.
Report findings before writing any code.
5b. Scope check — only implement what the task describes.
The task's summary and description fields define the full scope of work for this session. If the description references or links to external documents (evaluation docs, design specs, RFCs), treat them as background context only — do not implement items from those docs that go beyond what the task's own description asks for. Referenced docs often describe multi-task plans; implementing the entire plan collapses future tasks into one PR and defeats dependency ordering.
Delegate the work to the chosen subagent(s).
Implement, commit, and mark criteria done. Work through the acceptance criteria from step 1 as your checklist — one commit per criterion is the default. For each criterion in order:
Implement the changes that satisfy it
Commit and mark the criterion done atomically using
tusk commit --criteria:tusk commit <id> "<message>" "<file1>" ["<file2>" ...] --criteria <cid>An alternative
-mflag form is also supported (useful when file paths come first):tusk commit <id> "<file1>" ["<file2>" ...] -m "<message>" --criteria <cid>This runs
tusk lint(advisory — never blocks), stages the listed files, commits with the[TASK-<id>] <message>format and Co-Authored-By trailer, and marks the criterion done — all in one call. The criterion is bound to the new commit hash automatically. Duplicate[TASK-N]prefixes in the message are stripped automatically, and bare--separators are silently ignored.Always quote file paths — zsh expands unquoted brackets (
[id],[slug]) as glob patterns before the shell passes arguments totusk commit. Any path component containing[,],*,?, or spaces must be wrapped in double quotes (e.g.,"apps/api/[id]/route.ts").Avoid backticks and unescaped
$in commit messages — even inside double quotes, zsh and bash treat backticks as command substitution and$VAR/$(…)as variable expansion. A message that references code (e.g. explaining aflatMap { $0.isEmpty ? nil : $0 } ?? "US"change) fails withzsh: parse error near '}'before tusk ever sees the args. Drop the backticks (use plain identifiers) or escape every metacharacter — double-quoting alone does not protect them. This is the same class of zsh-quoting hazard as the file-paths note above, just hitting the message argument instead.Grouping criteria: 2–3 genuinely co-located criteria (e.g., a schema change and its migration) may share one commit — use one
--criteriaflag per ID:tusk commit <id> "<message>" "<file1>" ["<file2>" ...] --criteria <cid1> --criteria <cid2>Always include a brief rationale in the commit message when grouping. Never bundle all criteria onto a single end-of-task commit. Exception: if several criteria all land in one new file or one inseparable file-local change, bundle them in one commit with an explicit rationale instead of truncating/restoring the file just to simulate separate commits.
If a criterion does not apply to the implementation path you chose (e.g., a mutually-exclusive "do X OR document why exempt" pair where you did X), use
tusk criteria skip— NOTtusk criteria done --skip-verify:tusk criteria skip <cid> --reason "not applicable: chose <chosen-branch> over <skipped-branch>"done --skip-verifystamps the criterion with HEAD's commit hash, leaking an unrelated commit into the audit trail and triggering "shares commit" warnings between unrelated criteria.skipsetsis_deferred=1with the rationale recorded indeferred_reason; thetask-donegate andv_criteria_coverageview exclude deferred criteria automatically, so the task closes cleanly. Reservedone --skip-verifyfor criteria that ARE satisfied but cannot be auto-verified (the cases below).If the task has no git-trackable file changes (e.g., a venv install, a runtime config change, an OS-level operation, or a DB-only deliverable like
tusk conventions update/tusk lint-rule add), skiptusk commitentirely — it requires at least one file argument and will fail with exit code 1 (usage error) if none are provided. Mark criteria done directly:tusk criteria done <cid> --skip-verifyOnce every criterion is marked done, the feature branch will have no
[TASK-<id>]commits to merge — close out via Step 12'stusk abandon <id> --reason completed --note "<rationale>"path rather thantusk merge(which refuses on an empty branch).If a criterion requires filing follow-up tasks (typical for investigation/triage tasks whose criteria read "file focused follow-up tasks covering each distinct break"), do NOT call
tusk task-insertdirectly. Dupe-check first so a freshly-filed sibling task isn't immediately superseded by an existing one:tusk dupes check "<proposed summary>"If the check returns a match, amend the existing task (e.g.,
tusk criteria add <id> "<criterion>"ortusk task-update <id>) instead of creating a new one. If no match is found, prefer/create-taskover a rawtusk task-insert—/create-taskruns the same dedup check, decomposes scope, and applies the project's task conventions in one call. Usetusk task-insertonly when scripting bulk inserts where the dedup step has already been done.After each
tusk commitin foreground mode, rungit status --shortto confirm your files were staged and committed — a zero-exit commit that produced no diff (e.g. all files were already tracked with no changes) will silently succeed without staging anything.Web contract coverage: API and frontend task domains should run both focused web tests and
npm run type-check. DTO/schema changes can pass Vitest while still breaking TypeScript contracts in Server Components, Client Components, or shared route types, so the domain gate must catch those regressions before commit or merge.If
tusk commitfails withpathspec did not match any files(exit code 3, git-add error), first check whether the file was already committed in a priortusk commitcall for this task (e.g., when all changes go into a single file committed with earlier criteria), or whether the file was removed viagit rm(which stages the deletion —tusk committhen can't find the path to re-add). In either case,git add && git commitwould also fail — just mark the remaining criteria done directly:tusk criteria done <cid> --skip-verifyIf the error is a genuine pathspec mismatch (not an already-committed file), always pass file paths relative to the repo root (e.g.,
ios/SomeFile.swift, notSomeFile.swiftfrom insideios/). If the error persists, fall back to a path-limited commit:git add -- "<file1>" ["<file2>" ...] git commit -o -- "<file1>" ["<file2>" ...] -m "[TASK-<id>] <message>" --trailer "Co-Authored-By: Codex Sonnet 4.6 <noreply@anthropic.com>"git commit -o -- <files>limits the commit to the listed paths so unrelated pre-staged changes cannot leak into the task commit. Then mark criteria done withtusk criteria done <cid> --skip-verifyas usual.If
tusk commitfails withpathspec '…' is beyond a symbolic link(exit code 3), the path lives under a symlinked directory thatgit addrefuses to traverse. In tusk's own repo this hits any path under.agents/skills/<name>/, because each skill is a symlink toskills/<name>/. Retry with the real source path:tusk commit <id> "<message>" "skills/<name>/SKILL.md" --criteria <cid>More generally: if
ls -laon any parent directory shows it is a symlink, use the link's target path instead.If a pre-commit auto-formatter (e.g.
black,ruff --fix,prettier,gofmt) rewrites a staged file in-place,tusk commitdetects the index/working-tree divergence, re-stages the reformatted content, and retries the commit exactly once — no manual intervention required. If the retry also fails (the formatter produces unstable output on every run), bypass hooks with:tusk commit <task_id> "<message>" "<file>" --skip-verifyIf the commit removes a file from git tracking (i.e., the staged change is a
git rm --cacheddeletion, not a file modification), do NOT usetusk commit— it retries gitignored paths withgit add -f, which re-adds the file and defeats the deletion. Usegit commitdirectly:git commit -m "[TASK-<id>] <message>" --trailer "Co-Authored-By: Codex Sonnet 4.6 <noreply@anthropic.com>"Then mark criteria done with
tusk criteria done <cid> --skip-verify.If
tusk commitexits 6 (blocking lint violation) — the commit did NOT land. A non-advisory lint rule fired (Rule 1 raw sqlite3, Rule 3 hardcoded DB path, Rule 11 bad SKILL.md frontmatter, Rule 16 DB-backed blocking rules, Rules 18/19 MANIFEST drift, Rule 21 multi-trailing-newlines, etc.). The violating rule's output is printed verbatim — fix it, then retrytusk commit. Advisory-only rules (Rule 13 VERSION bump missing, Rule 15 big-bang commits, Rule 17 DB-backed advisory, etc.) still print WARN lines but do NOT exit non-zero and do NOT block. If the violation is a known false positive or pre-existing state you can't resolve in this commit, bypass with--skip-lint(lint only) or widen to--skip-verify(lint, tests, and pre-commit hooks):tusk commit <id> "<message>" "<file>" --skip-lint --criteria <cid>Lint output during commit is now filtered: only rules with violations print — passing rules are suppressed. If the last lint pass was clean, you won't see any lint output at all.
If
tusk commitexits 5 (test_command timeout) — the configuredtest_commandexceeded its timeout and was killed before producing an exit code. The stderr message names the resolved timeout and source. The resolution chain isTUSK_TEST_COMMAND_TIMEOUTenv var >config.test_command_timeout_secintusk/config.json> default (240s). If the failure is just slow first-run compilation (cold xcodebuild, Bazel cold cache, large Rust compile), retry with a per-invocation override:TUSK_TEST_COMMAND_TIMEOUT=600 tusk commit <id> "<message>" "<file>" --criteria <cid>If the slow path is permanent for this project, raise
test_command_timeout_secintusk/config.jsoninstead of overriding on every call. Do not blindly raise the timeout when the command genuinely hangs (e.g. waiting on interactive input or a missing dependency) — make the command non-interactive and fix the underlying hang first.If
tusk commithard-fails because tests fail (exit code 2 —test_commandis set and returned non-zero), first verify the failure is not pre-existing before entering the diagnosis loop:Pre-existing failure check — run the tests against HEAD with any local changes safely set aside:
tusk test-precheckOr pass an explicit command when the config-resolved one isn't what you want to check against:
tusk test-precheck --command "<test_command>"tusk test-precheckresolves the test command from--command, thenconfig.test_command, thentusk test-detect. When the working tree is dirty it stashes local changes under a uniquely-named entry, runs the test against HEAD, and pops that entry by reference — never by top-of-stack. When the working tree is clean it runs the test directly without touchinggit stashat all. Output is JSON on stdout:{pre_existing, exit_code, test_command, stashed}; the test command's own output is redirected to stderr so programmatic callers canjson.loads(stdout)directly. Do not fall back to the rawgit stash && … ; git stash popsnippet — when the tree is clean, the emptygit stashbecomes a no-op andgit stash popwill pop a stale foreign entry and silently trash unrelated state. If precheck exits non-zero, it prints a recovery message on stderr (always including the stash message, when one was created) so you can finish the pop manually; it never silently falls through with changes orphaned in the stash list.If
pre_existingistrue— the failure is pre-existing and unrelated to your changes. Skip the diagnosis loop entirely. Do not attempt to fix tests in files you did not modify during this session. Fall back immediately to:git add -- "<file1>" ["<file2>" ...] git commit -o -- "<file1>" ["<file2>" ...] -m "[TASK-<id>] <message>" --trailer "Co-Authored-By: Codex Sonnet 4.6 <noreply@anthropic.com>"Then mark criteria done with
tusk criteria done <cid> --skip-verify. The-o -- <files>form is required here too; a plaingit commitwould include any unrelated paths that were staged before this task.If
pre_existingisfalse— your changes introduced the failure. Proceed with the diagnosis loop below. Do not modify any code until you've completed steps 1–2:
- Read the full test output — scroll through the entire failure log. Do not make any code changes until you understand what failed and why.
- Trace the root cause — open the relevant source files and identify the exact lines responsible for the failure.
- Implement a fix — make the minimal change required to address the root cause.
- Retry
tusk commitwith the same arguments.
Repeat up to 3 times. If tests still fail after 3 attempts, run
tusk skill-run cancel <run_id>, surface the full failure output and a summary of what was tried to the user, then stop — do not continue looping.- Log a progress checkpoint:
tusk progress <id> --next-steps "<what remains to be done>"- All commits should be on the feature branch (
feature/TASK-<id>-<slug>), NOT the default branch.
The
next_stepsfield is critical — write it as if briefing a new agent who has zero context. Include what's been done, what remains, decisions made, and the branch name.Schema migration reminder: If the commit adds or modifies a migration in
bin/tusk-migrate.py(or bumpscmd_init's fresh-DBuser_versionstamp inbin/tusk), runtusk migrateon the live database immediately after committing.Review the code locally before considering the work complete.
Verify all acceptance criteria are done before pushing:
tusk criteria list <id>If any criteria are still incomplete, address them now. If a criterion was intentionally skipped, note why in the PR description.
Run convention lint (advisory) —
tusk commitalready runs lint before each commit. If you need to check lint independently before pushing:tusk lintReview the output. This check is advisory only — violations are warnings, not blockers. Fix any clear violations in files you've already touched. Do not refactor unrelated code just to satisfy lint.
Run
/review-commits— check the review mode first:tusk config review- mode = disabled (or review key missing): skip review, proceed to step 12.
- mode = ai_only: run
/review-commitsby following the instructions in:Read file: <base_directory>/../review-commits/SKILL.md for task <id>
AfterWarning: Do NOT spawn a
pr-review-toolkit:code-revieweragent directly as a shortcut. That agent receives only a manually reconstructed diff — not the realgit diffoutput — which causes false-positive review findings. The/review-commitsskill exists specifically to fetch and pass the real diff verbatim; bypassing it removes that safeguard./review-commitscompletes with verdict APPROVED, proceed to step 12. If verdict is CHANGES REMAINING, runtusk skill-run cancel <run_id>, surface the unresolved items to the user, and stop.
Finalize — merge, push, and run retro. Execute as a single uninterrupted sequence — do NOT pause for user confirmation between steps:
tusk merge <id> --session $SESSION_IDtusk mergecloses the session, merges the feature branch into the default branch, pushes, deletes the feature branch, and marks the task Done. It returns JSON including anunblocked_tasksarray. If there are newly unblocked tasks, note them in the retro.Already-merged path: If the feature branch was previously merged and deleted (e.g. via a PR that was merged in another session),
tusk mergedetects this automatically when you are on the default branch — it printsNote: TASK-<id> — no feature branch found; already on '<branch>'. Branch was previously merged., closes the session, pushes, and marks the task Done without re-merging. Iftusk mergeexits 0 in this scenario, proceed to/retroas normal.Diverged branch — rebase fallback: If
tusk mergeexits non-zero because the feature branch has diverged from the default branch (fast-forward-only merge not possible), run:tusk merge <id> --session $SESSION_ID --rebase--rebaserebases the feature branch onto the default branch before merging. If the rebase produces conflicts, resolve them (git rebase --continue) and retry.Not-on-default fallback: If
tusk mergeexits non-zero withNo branch found matching feature/TASK-<id>-* or worktree-TASK-<id>-*and you are NOT on the default branch, switch to the default branch first (git checkout <default_branch>), then retrytusk merge <id> --session <session_id>.Sibling-worktree DB fallback: If the default branch is checked out in a sibling worktree and the primary checkout is unusable, run the merge from the sibling worktree while pinning tusk to the primary repo's DB:
TUSK_PROJECT=<primary_repo_path> tusk merge <id> --session $SESSION_ID --rebaseThis is the correct fallback when running
tusk mergefrom the sibling worktree fails withno such table: task_sessions: that worktree has the git state needed for the merge, but tusk resolved its database relative to the sibling CWD.TUSK_PROJECTkeeps tusk pointed at the primary repo's project database while git commands operate in the current worktree.PR mode: If the project uses PR-based merges (
merge.mode = prin config, or when passing--pr), use:tusk merge <id> --session $SESSION_ID --pr --pr-number <N>This squash-merges via
gh pr mergeinstead of a local fast-forward.No-commit closure (
wont_do/duplicate/completed): If the task should be closed without shipping any code, usetusk abandoninstead oftusk merge:tusk abandon <id> --reason wont_do|duplicate|completed --session $SESSION_ID [--note "<rationale>"]Three reason values are accepted:
wont_do— an evaluation/spike whose answer is "don't do it".duplicate— the task turns out to overlap an already-tracked one.completed— the goal was met but no[TASK-N]commits land on the default branch. Two sub-cases:- convergent-completion (issue #580): separate work landing on the default branch between filing and pickup already satisfied the goal, so there is nothing left to ship.
- DB-only deliverable (issue #669): the deliverable is a SQLite row written via a tusk subcommand (
tusk conventions update,tusk conventions add,tusk lint-rule add,tusk glossary set-definition, etc.) — the feature branch is intentionally empty because nothing in the working tree changes.
Pass
--note "<rationale>"in both cases and reference the converging task(s)/commit(s) or the DB write performed —tusk abandonrecords it ontask_progressas[abandon: completed] <note>, which is the audit signal that distinguishes this case from a normaltusk mergeclose (no[TASK-N]commits will be on the default branch for this task either).
tusk abandonswitches off the feature branch, deletes it (force), closes the session, and marks the task Done with the givenclosed_reasonin one call. Refuses if the feature branch has commits not on the default branch — in that case usetusk mergeto ship the work, or delete the branch manually if you really want to discard it. The optional--noterecords the decision rationale ontask_progressso the audit trail survives. Aftertusk abandonexits 0, run/retroexactly as you would aftertusk merge.After
tusk merge(ortusk abandon) exits 0, close out the /tusk skill-run so its cost is captured before/retrostarts its own run:tusk skill-run finish <run_id>Then emit the canonical end-of-run summary before handing off to /retro:
tusk task-summary <id> --format markdownThis prints a single markdown block with the task identity, closed reason, total cost, wall/active duration, diff stats (files changed, lines added/removed, commit count), criteria counts, review pass count, and reopen count. Show it verbatim to the user — do not re-render or summarize it. Runs on both the merge and abandon paths; diff stats are filtered to commits that reference
[TASK-<id>]so shared-branch pollution never appears in the numbers.Then run
/retroimmediately — do not ask "shall I run retro?". Invoke it to review the session, surface process improvements, and create follow-up tasks.
Other Subcommands
If the user invoked a subcommand (e.g., /tusk done, /tusk list, /tusk blocked), read the reference file:
Read file: <base_directory>/SUBCOMMANDS.md
Skip this section when running the default workflow (no subcommand argument).