Run parallel AI code reviewers against the task's git diff, fix must_fix issues, and defer or dismiss suggestions
Orchestrates parallel code review against the task's git diff (commits on the current branch vs the base branch). Spawns one background reviewer agent per enabled reviewer in config, monitors completion, fixes must_fix findings, handles suggest findings interactively, and creates deferred tasks for defer findings.
Use
/create-taskfor task creation — handles decomposition, deduplication, criteria, and deps. Usetusk task-insertonly for bulk/automated inserts.
Optional: /review-commits <task_id> — if omitted, task ID is inferred from the current branch name.
tusk config
Parse the returned JSON. Extract:
review.mode — if "disabled", print "Review mode is disabled in config (review.mode = disabled). Enable it in tusk/config.json to use /review-commits." and stop.review.max_passes — maximum fix-and-re-review cycles (default: 2)review.reviewers — list of reviewer objects (each with name and description fields). If empty, a single unassigned review will be used.review_categories — valid comment categories (typically ["must_fix", "suggest", "defer"])review_severities — valid severity levels (typically ["critical", "major", "minor"])task_types — list of valid task type strings. Resolve the best type for deferred tasks now: prefer "refactor", then "chore", then the first entry that is not "bug". Store as DEFERRED_TASK_TYPE. If the list is empty or every entry is "bug", set DEFERRED_TASK_TYPE = null.If a task ID was passed as an argument, use it. Otherwise, infer from the current branch:
tusk branch-parse
Returns {"task_id": N} on success. If the command exits 1 (branch doesn't match pattern), ask the user to provide a task ID.
Verify the task exists and capture its domain:
tusk -header -column "SELECT id, summary, status, domain FROM tasks WHERE id = <task_id>"
If no row is returned, abort: "Task <task_id> not found."
Store the task's domain value (may be NULL/empty — this is used to filter reviewers in Step 5).
Determine the base branch and compute the diff:
DEFAULT_BRANCH=$(tusk git-default-branch)
CURRENT_BRANCH=$(git branch --show-current)
git diff "${DEFAULT_BRANCH}...HEAD"
If the diff is empty — whether on the default branch or on a feature branch whose commits have already been merged (fast-forward or otherwise) — attempt to recover the correct range by scanning git log for [TASK-<id>] commits (where <id> is TASK_ID from Step 2):
TASK_COMMITS=$(git log --format="%H" --grep="\[TASK-${TASK_ID}\]" -n 50)
If TASK_COMMITS is non-empty, construct the range from the oldest to the newest matching commit:
NEWEST_COMMIT=$(echo "$TASK_COMMITS" | head -1)
OLDEST_COMMIT=$(echo "$TASK_COMMITS" | tail -1)
git diff "${OLDEST_COMMIT}^..${NEWEST_COMMIT}"
Use this diff (and this range) going forward — including for the --diff-summary passed to tusk review start and for any re-review diff stat checks in Step 8.
If TASK_COMMITS is empty (no [TASK-<id>] commits found in recent history), stop with:
No changes found —
[TASK-<task_id>]commits not detected in recent git log. The diff range cannot be determined automatically. Confirm the correct commit range manually and re-run.
If the diff is still empty after the TASK-commit recovery, report "No changes found compared to the base branch." and stop.
Capture the diff only to check for emptiness and to generate the --diff-summary for tusk review start. Do not pass the diff to reviewer agents — they will fetch it themselves via git diff to avoid transcription errors.
Start a review record for the task. This creates one code_reviews row per configured reviewer (or one unassigned row if no reviewers are configured):
tusk review start <task_id> --diff-summary "<first 120 chars of diff summary>"
The command prints one line per created review. Example output for 3 configured reviewers:
Started review #12 for task #42 (reviewer: general): Fix login bug
Started review #13 for task #42 (reviewer: backend): Fix login bug
Started review #14 for task #42 (reviewer: security): Fix login bug
Parse every line to collect all review IDs. Do not stop after the first line.
Store the mapping: reviewer name → review_id (e.g., {"general": 12, "backend": 13, "security": 14}).
Only when the diff is non-empty and reviews have been started in Step 4, proceed with the steps below.
Important: Background reviewer agents run in an isolated sandbox and do not inherit the parent session's tool permissions. Approving Bash in this conversation does not grant Bash access to spawned agents. The
permissions.allowblock in.claude/settings.jsonis the only reliable way to grant tool access in agent sandboxes — it applies to all subagents spawned from this project, regardless of what is auto-approved in the current session.
For small or documentation-only diffs (fewer than ~200 lines changed, or only non-code files such as .md, .json, .yaml): skip agent spawning and perform an inline review instead. Read the diff yourself, evaluate it against the reviewer focus areas, and record the result directly:
# Approve with no findings:
tusk review approve <review_id> --note "Inline review: small/docs-only diff, no findings."
# Or if changes are needed:
tusk review request-changes <review_id>
# Then add comments as needed:
tusk review add-comment <review_id> "<description>" --file "<file>" --line-start <line> --category <category> --severity <severity>
After recording the inline decision, skip directly to Step 6.
For all other diffs: verify the required agent sandbox permissions are configured before spawning reviewer agents. Run:
python3 -c "
import json, sys