taskwarrior — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited taskwarrior (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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.
Default. Use the named report:
task <filter> denseExamples:
task project:pickled-claude-plugins dense
task +followup dense
task dense # bare = all pending, urgency-sortedCustom 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.
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>.urgencyYou 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.
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).
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.
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:
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.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).
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.
task <filter> count returns a single integer, ~3 chars.task summary is a project-by-project rollup, very dense.task projects lists all project names with counts.task tags lists all tags with counts.| 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 |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.