Taskwarrior Token-Dense Recipes
Use these recipes whenever you invoke task on the user's behalf. They cut output size 4-10× compared to default formats.
Companion config: This skill assumes ~/.taskrc has the named dense report defined and verbose= configured to suppress override-echo noise. If task (with no args) doesn't run a dense report, see the design doc at docs/superpowers/specs/2026-05-08-taskwarrior-token-density-design.md.
Listing tasks
Default. Use the named report:
task <filter> dense
Examples:
task project:pickled-claude-plugins dense
task +followup dense
task dense # bare = all pending, urgency-sorted
Custom shape, when you need different columns or truncation:
task <filter> export | jq -r '.[] | "\(.uuid[0:8]) [\(.project // "-")] \(.tags // [] | join(",")) \(.due // "-") \(.description[0:80])"'
This produces ~120 chars/task. The dense report produces ~150 chars/task with column alignment. The jq form is for when you need a specific shape (e.g., longer description, different fields).
Never: raw task list with no filter. The 320+ pending tasks at default formatting is the largest single cost in your taskwarrior I/O.
Single-field lookup
When you need ONE field of a known task, use _get with a DOM reference (<uuid>.<field>):
task _get <uuid>.description
task _get <uuid>.project
task _get <uuid>.tags
task _get <uuid>.due
task _get <uuid>.urgency
You can also batch fields in one call:
task _get <uuid>.project <uuid>.tags <uuid>.due
_get returns the raw field value(s), no metadata, no formatting. Roughly 10× denser than task <uuid> info. Note the syntax: _get is the command and the DOM reference is its argument. NOT task <uuid> _get description (that returns empty).
Never: task <uuid> info when you only need one field. info dumps full metadata (entry/modified/uuid/status/etc.): useful when triaging, wasteful for field lookup.
Multi-task lookup
When inspecting several tasks at once, pass UUIDs (or short UUIDs) as space-separated positional args to export:
task UUID1 UUID2 UUID3 export | jq -r '.[] | "\(.uuid[0:8]) [\(.project // "-")] \(.description[0:120])"'
Or for richer output:
task UUID1 UUID2 UUID3 export | jq -r '.[] | "uuid: \(.uuid[0:8]) tags: \(.tags // [] | join(",")) due: \(.due // "-") desc: \(.description[0:80])"'
Short (8-char) UUIDs work fine: task c9dca83f 53c8850a b940d369 export is equivalent. Comma-separated forms (uuid:A,B,C) do NOT work in taskwarrior 3.4.2; they return empty.
Never: task A info && echo --- && task B info && echo --- && task C info && .... This pattern has been observed at 25,000+ chars per call (vs ~600 chars for the batched export equivalent).
Full-text search
task list | grep is unreliable because descriptions wrap across lines. Use export + jq:
# Search descriptions (null-safe)
task export | jq -r '.[] | select((.description // "") | test("PATTERN"; "i")) | "\(.uuid[0:8]) \(.description[0:120])"'
# Search annotations
task export | jq -r '.[] | select(.annotations[]?.description | test("PATTERN"; "i")) | "\(.uuid[0:8]) \(.description[0:120])"'
# Search both
task export | jq -r '.[] | select((.description // "") + " " + ((.annotations // []) | map(.description) | join(" ")) | test("PATTERN"; "i")) | "\(.uuid[0:8]) \(.description[0:120])"'
The "i" flag makes the test case-insensitive. Drop it for case-sensitive matches.
Durable references: UUID, not numeric ID
Numeric IDs are reused once a task completes, or whenever the pending list otherwise reorders — task 197 today may be a completely different task next month, or even a few tool calls from now if other tasks completed or unrelated background work touched taskwarrior in between. They're fine for same-session interactive use (task 197 done, or an annotate right after creating the task, with nothing in between). They are not fine for anything that outlives the current shell: park/handoff files, commit messages, memory files, PR descriptions, beans. A numeric ID written into one of those will silently point at the wrong task later.
Verify before you cite, and re-resolve before you act. This has caused real, repeated damage — annotate/done calls landing on unrelated tasks in other projects, needing task <uuid> denotate -- <text> cleanup afterward:
- Before writing a UUID into a durable artifact, confirm it actually resolves to the task you mean (
task <uuid> info, read the description back). A UUID is only as trustworthy as the citation that produced it — hand-typed from memory, copied from an earlier message, or captured before the task was edited elsewhere can all silently point at the wrong task despite being correctly formatted. - Before acting on a cached integer ID (
done,annotate,modify,depends), re-resolve it if it was captured more than a few commands ago in the same session. Don't trust that the number still means what it meant earlier.
Capture the UUID at creation time and cite that instead:
task add project:foo +bug "OAuth token refresh fails with 401 after 24h"
# Output: "Created task 197."
UUID=$(task entry.after:now-1m +bug export | jq -r '.[0].uuid[0:8]')
Short (8-char) UUIDs are fine everywhere a full UUID would go — task <uuid> info, task <uuid> done, and in prose (task c9dca83f).
Description-length convention
When creating tasks (task add ...), aim for descriptions ≤ 100 chars. Long context goes in annotations:
# Good: short title, then annotate context.
task add project:foo +bug "OAuth token refresh fails with 401 after 24h"
# Output: "Created task 197." That numeric ID is fine for a same-shell-session
# follow-up like the annotate below. If this task will be cited anywhere durable
# (handoff, commit, memory, PR), capture the UUID first — see above.
task 197 annotate "Repro: lifecycle.test.ts:124. Happens only with refresh-token rotation enabled. Suspect cache key collision between user_id and session_id; see retro 2026-04-19."
# Bad: paragraph as description.
task add project:foo +bug "OAuth token refresh fails with 401 after 24h. Repro: lifecycle.test.ts:124. Happens only with refresh-token rotation enabled. Suspect cache key collision between user_id and session_id; see retro 2026-04-19. Affects all users on the canary deployment, blocks GA. Fix: split cache namespace by token_type."
Soft target. Existing bloated descriptions decay naturally as tasks are completed or edited; no retroactive cleanup is required.
Other dense idioms
- Counts:
task <filter> countreturns a single integer, ~3 chars. - Project summary:
task summaryis a project-by-project rollup, very dense. - Project list:
task projectslists all project names with counts. - Tag list:
task tagslists all tags with counts.
Decision tree: what should I run?
| Need | Run |
|---|---|
| "What's pending in project X?" | task project:X dense |
| "How many open in project X?" | task project:X count |
| "What's on the backlog overall?" | task summary (then drill into a project with dense) |
"What does task <uuid> look like?" |
task <uuid> info (full metadata is fine for one task) |
"What's the description of <uuid>?" |
task _get <uuid>.description |
| "What about these 5 tasks?" | task A B C D E export | jq -r '...' (space-separated UUIDs) |
| "Find tasks mentioning 'oauth'" | task export | jq -r '.[] | select(.description | test("oauth"; "i")) | ...' |
| "Add a task" | task add project:X +tag "short title ≤100c" then optionally task <uuid> annotate "context" |
| "Mark done" | task <uuid> done |
| "Citing a task in a handoff/commit/memory/PR" | Use the UUID (short form OK), never the numeric ID — see Durable references |