Issue hierarchy

One nullable relation carries epics, tasks, and subtasks: an issue may name a parent, and issues nest arbitrarily. There is no separate epic type.

Create a child

$fp2 issue create \
> --title "<short-title>" \
> --parent <issue-reference> \
> --client-id "<stable-retry-id>" \
> --json

The server resolves the reference within the selected project and stores the full canonical ID. A parent is assigned only when the child is created — there is no re-parenting — so cycles are structurally impossible. Creating a child under a done or cancelled parent is a typed conflict.

When you hold a parent’s representation, its advertised create_child action carries the parent as a pre-filled constant, so you never type the ID.

Read the hierarchy

fp2 issue show returns:

  • parent — reference, title, and status, or null;
  • children — a bounded list of the most recently updated children;
  • children_total and children_open — the authoritative counts.

State ratios and completeness from the counts, never from the bounded list.

The close gate

A parent cannot transition to done while any child is open (todo, in_progress, or review):

  • compliant clients simply do not see the done action; the blocked entry explains with reason_code: "open_children" and the count;
  • a constructed or raced attempt is rejected with a typed issue_children_open conflict carrying the count and the currently legal actions;
  • the check is atomic with the status write, so a child created concurrently cannot slip under a closing parent.

Cancellation is deliberately not gated: abandoning a parent asserts nothing about its children’s records. Close or cancel the children, re-read the parent, and done reappears.

Retries are safe throughout: a child create replayed with its original client_id returns the original child even if the parent has since closed.