/taskwarrior:task-done
Close a task with full coordination hygiene: annotate with the landing commit, drain the tracker entry, optionally close the GitHub issue.
When to Use This Skill
| Use this skill when... | Use task-add / task-status / task-coordinate instead when... |
|---|
| Closing a task whose work has landed in a commit | Filing a brand-new task — use task-add |
Draining a linked blueprint tracker entry to done | Reading current queue state without mutating it — use task-status |
Closing a ghid-linked GitHub issue or commenting on a ghpr PR | Picking the next dispatch candidate from the queue — use task-coordinate |
| Closing many tasks in one pass (queue cleanup, triage sweep) | See Bulk-close patterns below — the naive for id in 1 2 3; do task $id done; done loop silently closes the wrong tasks |
Context
- Task CLI available: !
task --version
- Git repo detected: !
find . -maxdepth 1 -name '.git' -print -quit
- GH auth: !
gh auth status
Git probes (git remote, git rev-parse --short HEAD,
git branch --show-current) write to stderr in a no-git cwd, and stderr
from a Context backtick aborts the skill before its body runs. HEAD
commit, current branch, and remote-presence checks happen in the body
(Steps 2 and 5) via the Bash tool where 2>/dev/null and exit-code
handling are tolerated.
Parameters
Parse $ARGUMENTS:
$0 — task ID (required)
$1 — commit hash (optional; defaults to HEAD)
--no-gh — skip GitHub close/comment even when remote is present
--no-tracker — skip blueprint tracker drain
--drain-identity — also clear agent / pid / host / branch / worktree UDAs after closing (default keeps them as audit trail)
--no-coworker-marker — skip the /git:coworker-check --release step
Execution
Execute this workflow:
Step 1: Load the task
task "$TASKID" export | jq '.[0]'
Never use task $TASKID info or task $TASKID list — both can exit 1 and
cancel parallel siblings. export | jq returns valid JSON even when the
task is already closed (treat empty as "no such open task" and abort).
Capture: uuid, bpid, bpdoc, ghid, ghpr, tags, description, plus
identity UDAs agent, pid, host, branch, worktree, and start
(if the task was claimed via /taskwarrior:task-claim).
Capture uuid as $TASK_UUID — every mutating call from Step 2 onward
(including the post-close Step 6 query) addresses the task by $TASK_UUID,
never $TASKID (see .claude/rules/task-id-stability.md).
Step 2: Resolve commit hash
If $1 is unset, read git rev-parse --short HEAD. Confirm the HEAD commit
actually touches work this task covers before annotating — a stale HEAD is
a common footgun.
Step 3: Annotate and close
task "$TASK_UUID" annotate "landed: $COMMIT_SHORT $COMMIT_SUBJECT"
task "$TASK_UUID" done
Annotation first, then done — if close fails (e.g. dependencies), the
annotation is still captured.
Taskwarrior auto-stops a +ACTIVE task on done, so an explicit
task stop is not needed. The task transition removes +ACTIVE and
records the duration. If you want to drain the identity UDAs (so the
closed task does not retain the stamp), do so separately after the
close — see Step 4b below.
Step 4b: Drain identity UDAs (optional)
After the task is closed, optionally clear the identity stamp left by
the original claim:
task "$TASK_UUID" modify agent: pid: host: branch: worktree:
Default behaviour is to leave these set on closed tasks — the audit
trail of "who claimed and landed this" is useful in task-status
recently-completed reports. Drain them only when the user explicitly
asks (e.g. compliance / privacy hygiene), or when handing the queue
file off to another team.
Step 4: Drain the blueprint tracker
If bpdoc is set and points to a valid path, read the file and advance
its status marker in place. Typical patterns by tracker format:
- Feature-tracker JSON:
status: in_progress → status: done, append
evidence entry citing the commit
- Work-order markdown: check off the relevant bullet, add landed-in line
- PRP: flip the "Implementation" status field
Use Edit with a narrow old_string / new_string pair — do not rewrite
the file. When bpdoc references a shared tracker (manifest.json, global
feature-tracker), fall back to reporting "manual tracker update required"
rather than concurrent-write the shared file.
Step 5: Close linked GitHub items (optional)
When GitHub mode is active and --no-gh was not passed:
ghid set → offer gh issue close "$GHID" --comment "Closed by $COMMIT_SHORT (branch $BRANCH)"
ghpr set → offer gh pr comment "$GHPR" --body "Linked task closed: $COMMIT_SHORT"
Always confirm before mutating GitHub state — the user may want to close
the issue as part of the PR merge rather than ahead of time.
Step 5b: Drop the coworker-check marker
If the task was +ACTIVE (claimed via /taskwarrior:task-claim), the
matching git-side session marker should be released so destructive ops
in this clone are no longer guarded:
Use SlashCommand to invoke `/git:coworker-check --release`.
Skip when the user has more work in flight on this branch — releasing
the marker lifts the cross-agent guard.
Step 6: Report
Print:
- Task closed: id + description + new status
- Commit annotated:
$COMMIT_SHORT
- Tracker drained: path + diff summary, or "skipped"
- GitHub: "issue #N closed" / "PR #N commented" / "skipped"
- Unblocked siblings: any tasks whose
depends: pointed at this one now
free to start (query via task depends:"$TASK_UUID" export | jq). Once
completed, this task's numeric id becomes 0 and may be reassigned to
an unrelated task — a query keyed on the stale $TASKID here would check
dependencies against the wrong task. depends: accepts UUIDs, so
$TASK_UUID is the only safe form for this read.
Agentic Optimizations
| Context | Command |
|---|
| Load task | task "$TASKID" export | jq '.[0]' (capture .uuid as $TASK_UUID) |
| Annotate + close | Two separate calls (hook-friendly), both by $TASK_UUID |
| Blocked children | task depends:"$TASK_UUID" export | jq '.[]' |
| Skip empty-result failures | Always export | jq |
Quick Reference
| Step | Command |
|---|
| Load | task TASKID export | jq (capture uuid) |
| Annotate | task UUID annotate "msg" |
| Close | task UUID done |
| Check unblocked siblings | task depends:UUID export | jq '.[]' |
| GitHub close | gh issue close N --comment "msg" |
| PR comment | gh pr comment N --body "msg" |
Bulk-close patterns
Closing many tasks in one pass has two silent foot-guns. The obvious shape — for id in 1 2 3; do task $id done; done — reports success while doing the wrong thing.
Foot-gun 1: numeric IDs renumber after every task done
Numeric IDs are a display index over pending tasks. The moment one closes, every higher ID shifts down by one. A loop over numeric IDs closes the original first task, then keeps targeting wrong tasks as IDs slide underneath the iterator. No error surfaces.
Fix: use UUIDs (immutable). The same applies to any iterated state-changing op — annotate, modify, delete.
for id in 35 36 37 38 39; do task "$id" done; done
UUIDS=$(task status:pending project:myrepo export | jq -r '.[].uuid')
for u in $UUIDS; do task "$u" done </dev/null; done
Foot-gun 2: task done consumes loop stdin
task done reads from stdin (for confirmation prompts). In a shell for loop, the loop's input is also stdin — so task done eats subsequent iterations and the loop exits early, usually after one or two passes, with no error.
Symptom: a loop over 15 UUIDs reports "processed 15" but only 1 task closed.
for u in $UUIDS; do
task "$u" rc.confirmation=no done </dev/null
done
echo "$UUIDS" | xargs -I {} sh -c 'task rc.confirmation=no {} done'
Always pass rc.confirmation=no for batch closes
Without it, taskwarrior may prompt "this task is blocked by N other tasks, complete anyway? (yes/no)" per task, hanging the loop. rc.confirmation=no makes batch closes deterministic.
Annotate before done, not after
Once completed, a task's id becomes 0 and task <id> no longer addresses it — only the UUID does. Annotate first to keep the UUID-or-ID workflow uniform.
Finding tasks for the bulk close
Use export | jq (never list — it exits 1 on empty filters and cancels parallel siblings; see .claude/rules/parallel-safe-queries.md):
task status:pending project:myrepo +pr_ready export | jq -r '.[].uuid'
task status:pending export | jq -r '.[] | select(.project == null) | .uuid'
task status:pending export \
| jq -r '.[] | select(.description | test("\\[triage\\]")) | .uuid'
Canonical pattern
UUIDS=$(task status:pending project:myrepo +pr_ready export | jq -r '.[].uuid')
echo "$UUIDS" | xargs -I {} sh -c 'task rc.confirmation=no {} done'
Related
/taskwarrior:task-add — file a task (use depends: for sequential WO chains)
/taskwarrior:task-claim — claim a task before working on it (this skill closes a claimed task)
/taskwarrior:task-release — release a claim without closing (handoff)
/taskwarrior:task-status — see what's left
/git:coworker-check — the matching session marker that --no-coworker-marker controls
blueprint-plugin:feature-tracking — tracker format that bpdoc points at
blueprint-plugin:blueprint-docs-currency — companion discipline for the bpdoc update
.claude/rules/parallel-safe-queries.md — the export | jq idiom
.claude/rules/task-id-stability.md — why this skill resolves $TASK_UUID once and mutates by UUID
taskwarrior-plugin/docs/task-tracking.md — full lifecycle: depends: + auto-unblock pattern