taskwarrior-plugin/skills/install-native-hooks/SKILL.md
Install opt-in taskwarrior native on-add/on-modify/on-exit hooks: auto-stamp project, enforce claims, batch GitHub sync, maintain coworker markers. Use when hardening a local taskwarrior store.
npx skillsauth add laurigates/claude-plugins install-native-hooksInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Install the plugin's opt-in taskwarrior native hooks into the user's
taskwarrior hooks directory (<data.location>/hooks/). These are taskwarrior's
own on-add / on-modify / on-exit hooks — distinct from Claude Code hooks —
and run on every local task add / task modify / task exit, regardless of
whether the change came from a plugin skill.
This skill writes to the user's global taskwarrior config and is strictly
opt-in: it runs only when the user explicitly invokes it. Nothing else in the
plugin (no SessionStart hook, no chezmoi apply) installs these.
| Use this skill when... | Use a sibling skill instead when... |
| --------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| Hardening a local store so every task add gets auto-stamped + tag-checked | Closing stale issue/PR trackers — use task-reconcile |
| You want the hyphenated-tag warning enforced outside the plugin skills | Filing or claiming a single task — use task-add / task-claim |
| Hook | Fires on | Behaviour | | ---- | -------- | --------- |
| on-add-taskwarrior-plugin | task add | Auto-stamps project from the git toplevel / cwd basename when unset; auto-links the ghid UDA from a trailing #N in the description when unset (pure text, no network); warns (never rejects) on a hyphenated +tag in the description (the form taskwarrior mis-parses) |
| on-modify-taskwarrior-plugin | modify / annotate / done / start / stop | Stamps claim identity (agent/host/branch/worktree) when a task goes +ACTIVE without it (so a bare task start still carries the UDAs /git:coworker-check reads); warns on a claim takeover (agent change); expires a stale claim (drops +ACTIVE + drains identity when start exceeds the TTL); warns on a hyphenated tag in the modified description |
| on-exit-taskwarrior-plugin | every task exit (sees the whole changeset) | Batches GitHub sync — queues the UUID of each touched task carrying a ghid/ghpr linkage UDA to <data>/claude-plugin-ghsync.queue; a SessionStart drain (scripts/drain-ghsync-queue.sh, run by the drift probe) resolves them in one batched task export, busts the drift-probe TTL cache for the affected projects so the next stale-check re-polls in one batched gh pass, and clears the queue. Maintains the coworker marker — for a touched task carrying pid+worktree, writes <git-dir>/.claude-session-<pid> when it goes +ACTIVE without one (skip-if-exists, so the skill marker is never clobbered) and removes a now-stale marker after a raw stop/done |
All three fail open — on any error (missing jq, bad JSON) they pass the
task through unchanged and exit 0, so a broken hook never blocks task add. The
on-exit hook's stdout is advisory feedback only; its exit code is ignored.
The on-modify stale-claim expiry is tunable from the environment (native hooks inherit the caller's shell):
| Variable | Default | Effect |
| ------------------------------------ | --------- | ------------------------------------------------------------------------------- |
| CLAUDE_TASKWARRIOR_CLAIM_TTL_HOURS | 4 | Age (hours) past which a still-+ACTIVE task is expired on next touch |
| CLAUDE_TASKWARRIOR_NO_CLAIM_EXPIRY | (unset) | Set to 1 to disable stale-claim expiry entirely (stamping + warnings stay on) |
Expiry is opportunistic — it fires when a stale claim is touched by any modify, not as a guaranteed background sweep. The deterministic dead-PID release (laurigates/claude-plugins#1792) and scheduled reconcile (#1793) cover the not-touched case.
The on-exit hook is likewise tunable from the environment:
| Variable | Default | Effect |
| ------------------------------------- | ----------------------------------------- | ---------------------------------------------------------------- |
| CLAUDE_TASKWARRIOR_GHSYNC_QUEUE | <data.location>/claude-plugin-ghsync.queue | Path of the batch gh-sync queue file |
| CLAUDE_TASKWARRIOR_NO_GHSYNC_QUEUE | (unset) | Set to 1 to disable the gh-sync queue (and its drain) entirely |
| CLAUDE_TASKWARRIOR_NO_MARKER_UPKEEP | (unset) | Set to 1 to disable coworker-marker upkeep entirely |
on-exit has no before-image. Unlike on-modify (which sees the original and modified task), on-exit receives only the final state of each changed task. So "linkage changed" is approximated as "a touched task carries a
ghid/ghpr", and marker upkeep keys on the task'spidUDA (a taskwarrior subprocess has no live agent PID of its own). A pure rawtask starton a never-claimed task carries nopid, so its marker is left to/taskwarrior:task-claim.
task importbypasses native hooks. Taskwarrior does not runon-add/on-modifyhooks duringtask import. So/taskwarrior:task-reconcile's bulk close path (which usestask import) is unaffected by these hooks — they complement reconciliation, they do not gate it.
task --versionParse $ARGUMENTS:
--check — report which hooks are present without installing.--uninstall — remove the plugin's hooks.Execute this install workflow:
Run the installer in check mode to show the resolved hooks directory and current state:
bash "${CLAUDE_SKILL_DIR}/scripts/install-hooks.sh" --check --templates-dir "${CLAUDE_SKILL_DIR}/templates"
Read HOOKS_DIR= and the per-hook present/absent lines.
Because this writes to the user's global taskwarrior config, confirm with
AskUserQuestion (not a freeform prompt — see
.claude/rules/skill-execution-structure.md): show the hooks dir and the three
hooks, and offer install / cancel. Skip the confirm only if --check or
--uninstall was passed.
On confirmation:
bash "${CLAUDE_SKILL_DIR}/scripts/install-hooks.sh" --templates-dir "${CLAUDE_SKILL_DIR}/templates"
For --uninstall:
bash "${CLAUDE_SKILL_DIR}/scripts/install-hooks.sh" --uninstall --templates-dir "${CLAUDE_SKILL_DIR}/templates"
Confirm the hooks are executable and exercise them safely in a scratch store — add a throwaway task with no project and a hyphenated tag, confirm the project is stamped and the warning prints, then delete it.
Smoke test (blockquoted so the tag-lint skips the intentionally-broken tag):
TASKDATA="$(mktemp -d)" task add "smoke +bad-tag test"— the on-add hook stampsprojectand prints the hyphenated-tag warning. Remove the scratch dir afterwards.
Report INSTALLED= / REMOVED=, the hooks dir, and a reminder that the hooks
fail open and are bypassed by task import.
| Context | Command |
| ----------------- | --------------------------------------------------------------------- |
| Preview | bash scripts/install-hooks.sh --check --templates-dir templates |
| Install | bash scripts/install-hooks.sh --templates-dir templates |
| Uninstall | bash scripts/install-hooks.sh --uninstall --templates-dir templates |
| Resolve hooks dir | task _get rc.data.location → <that>/hooks |
| Flag | Purpose |
| ------------- | ------------------------------------------- |
| (none) | Confirm, then install all three hooks |
| --check | Report hooks dir + present/absent, no write |
| --uninstall | Remove the plugin's hooks |
/taskwarrior:task-reconcile — bulk close uses task import, which bypasses these native hooks (by design)/taskwarrior:task-add — the skill that already stamps project + warns on hyphenated tags; these hooks extend that to ad-hoc task addscripts/lint-taskwarrior-tags.sh — the docs-side guard for the same hyphenated-tag class.claude/rules/skill-execution-structure.md — AskUserQuestion confirmation gatedevelopment
Debug HTTP APIs: trace requests, inspect headers. Use when a request fails: check status first.
documentation
Render architecture diagrams from text sources. Use when documenting system topology.
tools
Inspect JSON payloads and extract nested fields. Use when parsing API responses.
tools
--- name: no-description allowed-tools: Read --- # No Description This skill has no description and must be dropped with a warning.