Local CLI issue tracker for todos, follow-ups, structured records, and the canonical pattern for decomposing multi-step work into a parent + subissues with acceptance criteria, dependency edges, and priority. Use when the user mentions a task to remember, a bug to track, an open question, anything that needs to outlive the current turn, or when planning multi-heartbeat work that needs `chainlink issue ready`-driven pickup across sessions. Includes guidance on writing descriptions future-you c...
Installs into .claude/skills of the current project.
Are you the author of Chainlink?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/jasoncarreira-chainlink)
---
name: chainlink
description: Local CLI issue tracker for todos, follow-ups, structured records, and the canonical pattern for decomposing multi-step work into a parent + subissues with acceptance criteria, dependency edges, and priority. Use when the user mentions a task to remember, a bug to track, an open question, anything that needs to outlive the current turn, or when planning multi-heartbeat work that needs `chainlink issue ready`-driven pickup across sessions. Includes guidance on writing descriptions future-you can act on (handles, success path, failure path) and idempotency tactics for actions that might fire twice (boundary firing, overlapping heartbeats, retried subagent completion). Pairs with five-whys (chainlink is the storage backend for RCA trees). Storage is local — issues live under a `.chainlink/` directory in the operator's repo.
---
<!-- desc: Local issue tracker for todos, follow-ups, and multi-step work decomposition with parent+subissue chains, acceptance criteria, and dependency edges. -->
# Chainlink
`chainlink` is a CLI issue tracker (Rust binary). Each repo / workspace has a
`.chainlink/` directory holding issues with id, title, labels, parent/child
relations, and comments. Everything is local — no GitHub, no server.
The five-whys skill uses chainlink as its storage backend (see
`five-whys/CHAINLINK_USAGE.md` for the RCA-specific commands). This skill is
the general-purpose CLI reference for everything else: todos, bug tracking,
follow-ups, parking-lot items, anything that needs to persist between turns.
## Contract
**Trigger**: User mentions a task, bug, open question, or multi-step plan that needs
to outlive the current session. Mimir notices work that deserves to be tracked across
heartbeats or that needs `chainlink issue ready`-driven pickup in a future session.
**Requires**: A cwd with a `.chainlink/` directory (or ancestor) — the agent's home
(where operator-tracked issues live), not the mimir source tree. The `chainlink`
binary is on `PATH`.
**Guarantees**:
- Issues created with enough context for future-mimir to act on: title + description
with handle (what to do), success path, failure path.
- Subissue trees have dependency edges (`chainlink issue block`) where ordering matters.
- Multi-heartbeat work has a parent issue with subissues decomposed before implementation
starts (planning-heartbeat-first discipline from `memory/core/50-heartbeat-patterns.md`).
**Does not**: Sync to GitHub; send notifications; auto-close on PR merge (stale-open
sweep is the mechanism, run weekly); initialize a new tracker without operator
instruction.
## Where to operate
Chainlink commands need a `.chainlink/` directory in the cwd or an ancestor.
The operator's home repo or a working directory will have one. If you get
``No .chainlink directory found``, you're in the wrong place — check the
operator's repo root for `.chainlink/`, or ask before initializing one.
To initialize a fresh tracker (only when the operator says so):
```bash
chainlink init
```
## Verifying availability
```bash
chainlink --version
```
If the command isn't found, the binary isn't installed in this environment —
report that to the operator rather than guessing.
## Day-to-day patterns
### Create a todo
```bash
chainlink issue create \
--label todo \
--label "for-jason" \
--priority medium \
"Re-check the simhi multi-session regression after the sim_threshold tune"
# Returns: Created issue #42
```
`--label / -l` is repeatable. Pick labels that match an existing scheme — query
``chainlink label list`` first if you're not sure what's in use.
**Attach a description on creation** with `--description / -d`:
```bash
chainlink issue create "Add timeout to dispatch loop" \
--description "Currently unbounded; need a 30s ceiling with retry." \
--priority high \
--label todo
```
For non-trivial descriptions (with `§`, parens, smart quotes, or multi-line),
use a heredoc — bare `--description "..."` breaks on shell-quoting (see
Failure modes below):
```bash
DESC=$(cat <<'EOF'
Multi-line description with §1.2 and (parens) and
"smart quotes" all preserved.
EOF
)
chainlink issue create "<title>" --description "$DESC" --priority medium --label todo
```
**Priority** is `low | medium | high | critical` (default: `medium`). Treat it
as load-bearing — filter on it later via `--priority high` to focus.
### Add notes / context
```bash
chainlink issue comment 42 "Initial run: 71% multi-session at 0.80 sim."
```
Comment takes a positional `<TEXT>` (no `--body` flag). For non-trivial bodies
with special characters, use the same heredoc pattern as `--description` above.
### List open work
```bash
# All open (default)
chainlink issue list
# Filter by status (flag is --status, NOT --state)
chainlink issue list --status all # open + closed
chainlink issue list --status closed
# Filter by label / priority
chainlink issue list --label todo
chainlink issue list --priority high
chainlink issue list --label todo --label for-jason
# JSON for scripting
chainlink issue list --label todo --json
```
### Pick what to work on next
```bash
# Unblocked issues only — primary "what's actually actionable?" command
chainlink issue ready
# Tool suggests next from heuristics
chainlink issue next
# Text search across issues — useful before creating to avoid duplicates
chainlink issue search "dispatch timeout"
```
`ready` correctly filters out blocked items AND closed-issue blockers (an issue
unblocks once its blocker closes). Prefer `ready` over `list` when you're asking
"what should I pick up now?"
### Look at one issue
```bash
chainlink issue show 42 # human-readable, shows Blocked-by / Blocking edges
chainlink issue show 42 --json # structured (for chaining with jq)
# Show the whole hierarchy (NOT a single issue's subtree — tree takes no positional ID)
chainlink issue tree
chainlink issue tree --status open
```
**Note**: `issue tree` does NOT accept a positional ID — it always shows the
whole hierarchy filtered by `--status` / `--quiet`. For a single issue's
children/relations, use `issue show <id>` (its Blocked-by / Blocking edges
show the immediate relationships).
`show` retains historical edges (an issue still lists a closed blocker in
its "Blocked by" output), while `ready` filters them — see Failure modes
for when this matters.
### Mark progress
```bash
chainlink issue label 42 in-progress
chainlink issue unlabel 42 todo
chainlink issue close 42 # finished
```
**Note**: `close` does NOT accept `--comment` or `-m` — if you want to leave a
closing note, run `chainlink issue comment 42 "..."` first, then `chainlink
issue close 42` separately.
### Subissues for breakdowns
When a todo is too big or has dependencies:
```bash
chainlink issue subissue 42 \
--label todo \
--description "Acceptance: alert fires on threshold cross + matching test." \
"Sketch the alert flow"
chainlink issue subissue 42 \
--label todo \
--description "Acceptance: dispatch path delivers payload + ack." \
"Wire it through to the dispatch path"
# Closing the parent doesn't auto-close children — close them as you go.
```
Each subissue should have a non-empty `--description` capturing acceptance
criteria — that's how a future session (or another agent) can pick up the
work without re-reading the parent. See "Subissue decomposition pattern"
below for the full workflow.
### Block / unblock relations
```bash
chainlink issue block 17 42 # 17 is blocked by 42 (don't work on 17 yet)
chainlink issue unblock 17 42
```
The live CLI argument order is `chainlink issue block <ID> <BLOCKER>`:
`<ID>` is the issue that should wait, and `<BLOCKER>` is the prerequisite.
Once issue 42 closes, issue 17 automatically appears in `chainlink issue
ready`. `issue show 17` still lists 42 in its "Blocked by" output as
historical record — trust `ready` for "is this actionable?", trust
`show`/`tree` for "what was this once depending on?"
## When to create a chainlink issue
Create one when:
- The user mentioned something they want you to remember beyond the current
conversation ("don't let me forget to call the dentist", "track down that
flaky test").
- You discovered a bug or rough edge worth fixing later but not now.
- A reflection / heartbeat surfaced a follow-up that needs a real owner.
- A 5-Whys analysis produced an action item (use the `rca` + `action-item`
labels — see `five-whys/CHAINLINK_USAGE.md`).
- A task is too big for one session and benefits from subissue decomposition
(see pattern below).
Don't create one when:
- It's a within-turn working note → use the Plan tool or just hold it in
context.
- It's a memory / preference about the user → store in saga via
`memory_store`, not chainlink.
- The user is just venting and didn't ask you to track anything.
## Subissue decomposition pattern
The canonical shape for any "this is too big for one session" task — two-phase
work split between a planning session and one or more implementing sessions.
### Phase 1 — planning session
1. **Create parent** with a description summarizing the goal and overall
acceptance criteria.
2. **Decompose** into subissues. Each subissue gets a non-empty
`--description` with its own acceptance criteria — sized so a future
session can pick it up without re-reading the parent.
3. **Add dependency edges** via `chainlink issue block <blocked> <blocker>`
when `<blocked>` must wait for `<blocker>` to complete.
4. **Mark parent in-progress** via `chainlink issue label <parent> in-progress`.
Acceptance for the planning session: parent has subissues, every subissue has
a non-empty description with acceptance criteria, dependency graph committed.
Half-decomposed parents are worse than not starting — if planning runs out
before decomposition finishes, comment on the parent and resume next session.
### Phase 2 — implementing sessions
```bash
chainlink issue ready # find unblocked subissues
chainlink issue show <subissue> # read its acceptance criteria
# ... do the work ...
chainlink issue close <subissue>
```
Pick the highest-priority unblocked subissue. Close on completion. The next
session repeats `ready` to find what's actionable now.
### Phase 3 — closing the parent
Once all subissues are closed, do any integration work (tests across the
decomposition, docs, the final PR if applicable), then close the parent.
## Querying open work
End-of-session is a good time to surface what's open. Prefer `ready` for
actionable work; use `list` when you need the full picture including blocked
items:
```bash
# What's actionable RIGHT NOW (unblocked)?
chainlink issue ready
# What's currently labeled todo / for-jason?
chainlink issue list --label todo --label for-jason --json \
| jq '.[] | "#\(.id): \(.title)"'
# Stale issues (no comment in N days) — useful for librarian sweeps
chainlink issue list --json | jq '.[] | select(.updated_at < "2026-04-04")'
```
If the operator asks "what should I work on?" or "what did we have open?",
``chainlink issue ready`` is the right starting point — not your memory,
and not `list` (which includes blocked items the operator can't actually
start on).
## Five-Whys integration
Five-whys reuses chainlink for tree storage. The labels are:
| Label | Meaning |
|---|---|
| `rca` | Part of a root cause analysis |
| `5-whys` | Root issue of a 5 Whys tree |
| `bedrock` | Leaf node — root cause found |
| `action-item` | Concrete fix derived from bedrock |
| `external-boundary` | Root cause outside your control |
| `accepted-tradeoff` | Known intentional choice — not a bug |
See `five-whys/CHAINLINK_USAGE.md` for the full tree-construction flow,
falsification cascades, and example.
## Conventions
- **Labels**: prefer existing labels over inventing new ones. ``chainlink
label list`` shows what's in use.
- **Titles**: imperative for todos ("Add timeout to dispatch loop"), past-
tense / observational for records ("Discord typing-indicator regression
fixed in commit 2ea9863").
- **Descriptions**: every subissue gets one; non-trivial parents get one.
Empty description = future session can't pick up the work.
- **Priority**: load-bearing. Default `medium`; reserve `high` for "blocks
shippable work" and `critical` for "service-affecting now".
- **Don't close issues you didn't open** without an explicit operator OK —
closing destroys the open-work signal.
- **Don't bulk-create**. Each chainlink issue should earn its keep. A pile
of micro-issues is harder to navigate than five well-titled ones.
## Writing descriptions future-you can act on
Heartbeat picks up issues by reading descriptions cold. Prose ("look into
the rate-limit thing later") is unanchored — a future heartbeat reads it,
shrugs, and moves on. Descriptions with **handles** ("investigate why
`oauth_quota_anomalous` has fired 5×/day on
`discord-100000000000000002` since 2026-05-03") are actionable on first
read. This applies equally to `-d/--description` on `create` and to the
acceptance-criteria bullets on subissue descriptions (see "Subissue
decomposition pattern" above) — same four questions, same handles.
A good description answers four questions:
1. **What's the trigger, named with handles?** Specific event names,
file paths, commit hashes, dollar thresholds, dedupe-key shapes —
anything a future heartbeat can grep for. Not "saga thing went
wrong" but "`saga_synthesis_skipped_boundary` fired 5×/24h on
synthesis turns since PR #32 (2026-05-06)".
2. **Why does this matter?** The reason it crossed the bar from noise
to signal. "Atoms aren't landing → silent memory loss" is a reason;
"felt off" is not.
3. **What's the success path?** The concrete next step if the
investigation finds something. "If the cause is the new dedup
window, the fix is bumping the floor in `_pre_message_hook`."
4. **What's the failure path?** Likely modes if the cause isn't what
you expect. "If it's not the dedup window, check the consolidator —
PR #32 also touched `_partition_turns`."
The handles matter most. With handles, future-you can grep
`events.jsonl`, query saga, or `git log` and find every record tied to
the issue. Without handles, the issue is unanchored and gets deferred
forever.
## Idempotency: design for the boundary firing twice
Chainlink issues can be picked up by overlapping heartbeats. Pollers
can emit duplicate events when a cursor wasn't saved before a crash.
Subagent completion notifications can arrive after a retry. Synthesis
turns can fire twice on the same session boundary. **Design actions so
they're safe to run twice.**
The wrong instinct is "I just got here, I should do the thing." The
right instinct is "I just got here, has the thing already been done?"
General tactics:
- **Tag durable artifacts with a unique key.** PR titles, commit
messages, and chainlink issue titles are good unique keys — grep
before acting.
- **Update cursors atomically and *after* the side effect.** Save the
poller cursor only once the events have been emitted; never
save-then-emit (a crash between would save the cursor but lose the
event). See `pollers/SKILL.md` for the cursor pattern.
- **Use mkdir / O_CREAT-style claims for cross-process work.** If a
marker file or directory exists, another instance is already
working — back off rather than collide.
- **Treat `send_message` and similar with care.** The harness
circuit-breaks near-duplicate sends within a turn — but design as
if it won't; the breaker is a safety net, not a correctness
guarantee.
### Mimir-specific idempotency surfaces
Several primitives have built-in idempotency you can lean on — plus a
couple that *look* idempotent but aren't:
- **`memory_store` is unique-key idempotent.** Same content twice → one
atom (deduplicated at the atom layer). Synth turns firing twice on
the same boundary don't create two atoms.
- **`applied_proposals.jsonl` is the legacy proposal-replay guard.**
Before re-applying a historical `state/proposed-changes.md` entry,
check whether its id is already in `state/applied_proposals.jsonl` —
if so, it landed already and re-applying is a foot-gun. Protected
memory/prompt proposals now go through `open_proposal` /
`submit_proposal` PRs instead.
- **`chainlink issue create` is *append-only*, not idempotent.**
Re-running `issue create "Same title"` produces a *new* issue with a
fresh id every time. **Always `chainlink issue search "<title-or-handle>"`
before creating** — this is the most common chainlink foot-gun.
- **`send_message` has harness-level dedup *within a turn* only.**
Across turns the breaker doesn't help; rely on unique keys in the
message itself if the same content might be generated twice.
- **`gh pr create` is *not* idempotent.** It happily makes a second
PR against the same branch. Always `gh pr list --head <branch>`
first.
## Failure modes
- ``No .chainlink directory found`` → wrong cwd; navigate to the operator's
repo or initialize via ``chainlink init`` if explicitly authorized.
- ``chainlink: command not found`` → binary missing from PATH. Surface to
operator; don't try to install yourself.
- ``unexpected argument '--state'`` (or similar `--state` / `--body` / `-m`
rejection) → flag-name surprises vs gh-style intuition. The CLI is its
own tool, not a gh clone. **Always check `chainlink <subcommand> --help`
when a flag fails** — it's exhaustive. The mimir-tracked surprises list
lives at `memory/issues/chainlink-cli-flag-surprises.md` (close has no
`--comment` or `-m`; list uses `--status` not `--state`; comment takes
positional `<TEXT>` not `--body`; create takes `--description / -d` not
`--body-file`; tree takes no positional ID).
- Comment / description bodies break on `§`, parens, or smart quotes →
shell-quoting bites. Use the heredoc-to-tmpfile pattern shown under
"Create a todo" above. Reference: `memory/issues/chainlink-comment-shell-quoting.md`.
- `issue show` says "Blocked by #N" but #N is closed → that's historical
record. Use `issue ready` to determine actionability; `show` retains
edges for audit. Reference: `memory/issues/chainlink-ready-vs-show.md`.
- Subissue depth gets unwieldy past ~3 levels — flatten or split into
separate trees if you find yourself going deeper.