IDD skills reach the issue tracker through a platform driver — the workflow's operations mapped to one tool's commands. This is the GitHub driver, implemented with the GitHub CLI (gh) and the only one implemented; .idd.yml selects it with platform: github. Skills inline these commands at the step where they run; this catalog is the contract they must match — when a command here changes, the skills follow.
- Data retrieval always uses
--jsonwith explicit field selection. Never parseghtext output — it is unstable across versions and locales. The field lists below are supersets; a skill selects only what it needs. - Mutations are verified by re-reading. After an edit/create, confirm with the corresponding read rather than trusting the exit code.
- Auth failures produce rich errors. When
ghis missing or unauthenticated: state what failed, thenTo fix: gh auth login, then the docs link. - Check the rate budget before batch loops (triage, auto-pilot):
gh api rate_limit --jq '{remaining: .rate.remaining, reset: .rate.reset}'— one call, both fields;resetis what an unattended loop pauses until. - Retry only transient failures, on bounded exponential backoff. Recoverable: 5xx, connection reset or timeout, a secondary rate-limit 403 (honour
Retry-After). Not recoverable: 401, 404, primary rate-limit exhaustion — rule 4's pause path, never backoff.gi-ratelimit.py --backoffcomputes the schedule; the loop lives with the caller.
| Operation | Canonical command |
|---|---|
| Verify authentication | gh auth status |
| Remaining API budget | gh api rate_limit --jq '{remaining: .rate.remaining, reset: .rate.reset}' |
| Repo merge strategy | gh repo view --json mergeCommitAllowed,squashMergeAllowed,rebaseMergeAllowed |
| Squash-commit message source | gh api repos/{owner}/{repo} --jq '{squash_merge_commit_title, squash_merge_commit_message}' |
| Caller's permission | gh repo view --json viewerPermission |
REST, not --json: gh repo view --json squashMergeCommitMessage answers Unknown JSON field. It is not interchangeable with the strategy row above — that answers is squash allowed, this whether the squash commit carries the PR body (PR_BODY) or the commit subjects (COMMIT_MESSAGES, the default); only this one decides whether B1 lands (#295). Writing PR_BODY requires title source PR_TITLE.
| Operation | Canonical command |
|---|---|
| Read one issue | gh issue view {N} --json number,title,body,labels,assignees,state,comments,createdAt,updatedAt,author |
| Resolution links | gh issue view {N} --json number,state,closedByPullRequestsReferences |
| List open issues | gh issue list --state open --json number,title,body,labels,assignees,state,updatedAt --limit 100 |
| List closed issues | gh issue list --state closed --json number,title,labels,closedAt --limit 50 |
| Create issue | gh issue create --title "{title}" --body "{body}" --label "{labels}" |
| Rewrite body (normalize) | gh issue edit {N} --body "{normalized_body}" |
| Add labels | gh issue edit {N} --add-label "{label1},{label2}" |
| Comment (backup, notice) | gh issue comment {N} --body "{text}" |
| Close with comment | gh issue close {N} -c "{reason}" |
| Reopen | gh issue reopen {N} |
| Operation | Canonical command |
|---|---|
| Create PR | gh pr create --title "{pr_title}" --body "{pr_body}" |
| Read one PR | gh pr view {N} --json number,title,body,baseRefName,headRefName,headRefOid,state,url,labels,reviews,statusCheckRollup,files |
| Merge readiness | gh pr view {N} --json mergeable,reviewDecision,statusCheckRollup,headRefOid |
| List merged PRs | gh pr list --state merged --json number,title,body,mergeCommit,headRefName --limit 50 |
| List open PRs | gh pr list --state open --json number,title,body,headRefName --limit 20 |
| Diff / changed files | gh pr diff {N} · gh pr diff {N} --name-only |
| Checkout PR head branch | gh pr checkout {N} |
| CI check status | gh pr checks {N} |
| Read PR body (before edit) | gh pr view {N} --json body |
| Update PR body | gh pr edit {N} --body "{body}" — read-modify-write only: fetch the body, apply the minimal change (e.g. prepend Closes #N), write back, verify per rule 2 |
| Merge identity (live base ancestry) | gh api repos/{owner}/{repo}/compare/{base_ref}...{verified_head} --jq .behind_by — merge only on 0 and an unchanged headRefOid |
| Squash-merge + clean up | gh pr merge {N} --squash --delete-branch --match-head-commit {verified_head} |
| Find PR for an issue | gh search prs "is:open" "Closes #{N}" --json number,state,url --limit 5 |
Merges bind a head and a base (#516): {verified_head} is the SHA the CI verdict covers; compare the base branch name (its live tip), never baseRefOid (the base at the PR's last sync). Patch-id equality never replaces fresh integration checks.
For operations with no first-class gh subcommand — uploading issue assets, say — use gh api directly and note the endpoint inline (gh api repos/{owner}/{repo}/contents/.github/issue-assets/{filename} ...). Prefer a catalog operation when one exists.
Porting IDD to another tracker (GitLab, Gitea, …) is deliberately scoped: write the equivalent driver document — docs/platform-<name>.md mapping every operation in this catalog to the new tool (e.g. glab issue view), then update the inlined commands in the skills to match it. The catalog above defines exactly which operations must exist; anything the new tool cannot express is a documented gap, not a silent one. Until that second document exists, the preamble's "only implemented driver" is also the only valid platform value.