Design Constraints
Authorization gate: preventing untrusted issue injection
Agents must never auto-action issues from arbitrary contributors. The approved work queue requires an explicit gate:
- Project board membership (coarse gate) — only issues added to the GitHub Project are visible to
backlog.py pull. Issues opened by external contributors exist in the repo tracker but are invisible to agents until a maintainer adds them to the project. - Label filter (fine-grained) — within the project, only issues carrying an
agent:actionablelabel (or equivalent) enter the agent's working set. This prevents half-triaged items from being picked up prematurely. - Author allowlist (optional hardening) —
.claude/config.jsonorpyproject.tomlcan list approved GitHub logins.backlog.py pullskips issues from authors not on the list even if they're in the project. Useful for repos with many collaborators.
The combination means: random drive-by issues stay in the repo's issue tracker, never enter the agent's local cache, and never get worked. A maintainer explicitly approves work by adding the issue to the project board and labeling it.
Cache-first reads: minimizing API pressure
Local .claude/backlog/*.md files are the fast read path for agents. GitHub API is reserved for writes and explicit syncs. This avoids rate-limiting, network latency, and unnecessary API pressure from agents querying backlog state during sessions.
Cache hit (read local, skip GH API):
- Recent local write — frontmatter
last_syncedis within a freshness window (e.g., 10 min) and action is read-only (list,find_item, reading item details for grooming/planning). The local file is authoritative for the current session. - Same branch, same session — if the item was modified on the current git branch, local changes are the latest state. No reason to round-trip to GH.
- Read-after-write — immediately after
add,update, orgroomwrites locally, the local file reflects the latest state. Subsequent reads within the session use it directly. - No issue number — Ideas and un-synced items have no GH issue. Local is the only source.
listwithout--with-status— defaultlistreads only local files. Status label lookup (which requires GH API) is opt-in via--with-status.- Offline / no token —
_try_get_github()returnsNone. Fall back to local cache for all reads, warn once per session.
Cache miss (must hit GH API):
list --with-status— needs label state from GHclose/resolve— mutates GH statepull— explicit cache refresh from GHsync/push— writes local state to GH- Fresh clone or new branch with no local files — must
pullfirst
Design principle: Agents working locally against backlog.py list and backlog.py groom should never trigger API calls as a side effect of reading. Network operations are explicit (pull, push, sync, --with-status).
Fact-Check
Date: 2026-02-27 Claims checked: 8 | VERIFIED: 5 | REFUTED: 1 | PARTIALLY VERIFIED: 2
Claim 1: "Current architecture has BACKLOG.md as primary source of truth"
Verdict: REFUTED
Evidence: BACKLOG.md does not exist (glob search returned empty). Code reads from .claude/backlog/ per-item files via _parse_backlog_from_directory() (backlog.py:178-222).
Impact: Issue description overstates the problem — BACKLOG.md is already eliminated. The real issue is local-first vs GH-first flow.
Claim 2: "GitHub Issues as a secondary mirror"
Verdict: PARTIALLY VERIFIED
Evidence: add creates local file first, then GH issue (backlog.py:760-794). list reads only local files (backlog.py:872). sync creates GH issues for items missing them. Flow is local-first despite docstring (line 17) claiming "GitHub Issues are the source of truth."
Citation: Codebase analysis (backlog.py lines 760-794, 866-877)
Claim 3: "A second session sees stale markdown files"
Verdict: VERIFIED
Evidence: list calls parse_backlog() which reads only the local directory. No automatic GH sync on read. pull exists but must be invoked manually.
Citation: Codebase analysis (backlog.py:225-231, 1912-1943)
Claim 4: "close happens via Fixes #N — GitHub auto-closes on merge"
Verdict: VERIFIED Evidence: GitHub docs confirm supported keywords (close, closes, fix, fixes, resolve, resolves) auto-close linked issues when PR merges to default branch. Enabled by default, configurable since Apr 2025. Citation: Linking a PR to an issue (accessed 2026-02-27)
Claim 5: "status lives in GitHub labels"
Verdict: VERIFIED
Evidence: Labels status:needs-grooming, status:in-progress used at issue creation (backlog.py:465) and fetched for display (backlog.py:809, 834).
Citation: Codebase analysis (backlog.py:465, 798-812, 1492-1502)
Claim 6: "priority lives in GitHub labels"
Verdict: VERIFIED
Evidence: Priority labels (priority:p1 etc.) created on issue creation (backlog.py:461-465).
Citation: Codebase analysis (backlog.py:461-465)
Claim 7: "Add pull/push commands: backlog.py pull"
Verdict: PARTIALLY VERIFIED
Evidence: pull already implemented (backlog.py:1912-1943). No push command exists — sync pushes groomed content but is not a full bidirectional push.
Citation: Codebase analysis (backlog.py:1912-1943; grep for "def push" returned no matches)
Claim 8: "Fixes #N convention handles closing automatically"
Verdict: VERIFIED Evidence: Same as Claim 4. Default branch only. Configurable since Apr 2025. Citation: Linking a PR to an issue (accessed 2026-02-27)
Summary of Impact on Scope
The issue description is partially stale — it describes a problem state (BACKLOG.md as primary) that no longer exists. The current architecture already uses per-item .claude/backlog/*.md files with GH Issue sync, labels for status/priority, and a pull command. The remaining work is narrower than described: flipping add and list to be GH-first, adding push, and removing local-file-as-primary dependency.
RT-ICA
Goal: Flip the backlog system from local-file-primary to GitHub-Issues-primary, with local .claude/backlog/ files as a derived read cache.
Decision: APPROVED (with reduced scope — fact-check found 1 REFUTED claim and 2 partially verified)
| # | Condition | Status | Info needed |
|---|---|---|---|
| 1 | Current architecture understood | AVAILABLE | Verified via codebase analysis |
| 2 | BACKLOG.md is primary source of truth | MISSING | REFUTED — BACKLOG.md doesn't exist. Issue description is stale. |
| 3 | GitHub Issues API access | AVAILABLE | PyGithub + GITHUB_TOKEN already in use |
| 4 | Label taxonomy (status/priority) | AVAILABLE | Already implemented |
| 5 | pull command (GH→local) | AVAILABLE | Already implemented (backlog.py:1912) |
| 6 | push command (local→GH) | DERIVABLE | sync does partial push; full push derivable |
| 7 | Authorization gate design | AVAILABLE | Described in local file |
| 8 | Migration path (local-first → GH-first) | MISSING | No migration plan |
| 9 | Offline/degraded-network fallback | MISSING | GH-first needs fallback strategy |
| 10 | Impact on dependent skills | DERIVABLE | create-backlog-item, work-backlog-item, groom-backlog-item |
| 11 | Fixes #N convention | AVAILABLE | Verified against GitHub docs |
| 12 | Revised scope statement | MISSING | Issue overstates current problems |
Missing inputs: Migration path, offline behavior strategy, revised scope reflecting already-implemented features.
Groomed (2026-02-27)
Reproducibility
Issue description is partially stale — BACKLOG.md no longer exists. Current architecture uses per-item .claude/backlog/*.md files with GitHub Issue sync. The core problem (local-first flow) is reproducible: list reads only local files, add creates local file before GH issue.
Priority
9/10 — P1 backlog system architecture issue. Blocks distributed team workflows; every teammate on different machine sees stale cache. Core infrastructure work that unblocks dependent skills (create-backlog-item, work-backlog-item, groom-backlog-item).
Impact
- Blocks: Distributed team workflows; multi-session consistency; agents working on same items across machines
- Bottleneck: Local files as primary truth; second session always sees stale data; no automatic sync
- Benefits: GitHub Issues as universal source of truth, multi-session collaboration, offline-first design possible
Scope
Current state (partially implemented):
- Per-item
.claude/backlog/*.mdfiles exist (BACKLOG.md already eliminated) - Labels for status/priority already in use
pullcommand exists (backlog.py:1912-1943)syncpushes groomed content but is not a fullpush
Remaining work (narrower than issue describes):
- Flip
addto create GitHub Issue first, write local cache from response - Flip
listto querygh issue listwith label filters, cache locally - Add
pushcommand for full local→GitHub sync - Define offline/degraded-network fallback behavior
- Create migration plan for existing items
- Update dependent skills (create-backlog-item, work-backlog-item, groom-backlog-item)
Already done (should be removed from scope):
- BACKLOG.md elimination
- Status in GitHub labels (status:needs-grooming, status:in-progress)
- Priority in GitHub labels (priority:p1, etc.)
pullcommand- Authorization gate design (documented in item file)
Output / Evidence
addcreates GitHub Issue first, writes issue number to local cachelistqueriesgh issue listwith status/priority label filters, caches results locallypushcommand syncs local edits → GitHub Issues- Offline fallback behavior defined and tested
- Migration plan documented and executed for existing P0/P1 items
- Dependent skills updated to call new add/list/push flows
Dependencies
- Depends on: Issue #283 (unify issue body template) — should be completed first to ensure issue body structure is stable
- Blocks: create-backlog-item, work-backlog-item, groom-backlog-item skills
- Related: Authorization gate design (documented in item file Design Constraints section)
Research
- GitHub auto-close via Fixes #N: VERIFIED — Linking a PR to an issue (accessed 2026-02-27)
- Auto-close configurable since Apr 2025: GitHub Changelog (accessed 2026-02-27)
Skills
- /backlog — main backlog CRUD interface
- /create-backlog-item — invokes backlog add
- /work-backlog-item — invokes backlog list, close, resolve, update
- /groom-backlog-item — invokes backlog groom
Agents
- @backlog-item-groomer — spawned by groom-backlog-item
Prior Work
- .claude/backlog/p1-backlog-system-redesign-github-issues-as-source-of-truth-wit.md (design constraints, authorization gate)
- .claude/backlog/p1-backlogpy-unify-issue-body-template-and-add-missing-structur.md (related issue #283)
- .claude/skills/backlog/scripts/backlog.py (1946 lines — primary implementation file)
Files
- .claude/skills/backlog/scripts/backlog.py (add:760, list:866, pull:1912, sync:979)
- .claude/skills/backlog/SKILL.md
- .claude/skills/create-backlog-item/SKILL.md
- .claude/skills/work-backlog-item/SKILL.md
- .claude/skills/groom-backlog-item/SKILL.md
Decision
APPROVED for grooming. Scope is narrower than original description. Three open questions for human input before planning:
- Offline/network fallback strategy (fail fast / read-only cache / queue for sync)
- Migration approach (auto-migrate on first run / manual / dual-write transition)
- Phased implementation preferred (add/list first → push → offline) or all-at-once
Note: GitHub label type:bug should be changed to type:feature — this is a redesign, not a bug fix.