
Issues Workflow
- 115 installs
- 62 repo stars
- Updated August 3, 2026
- terrylica/cc-skills
Use issues-workflow for development tasks
About
issues-workflow: A skill for development. This provides functionality for development workflows.
- issues-workflow
Issues Workflow by the numbers
- 115 all-time installs (skills.sh)
- +1 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #2,892 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/terrylica/cc-skills --skill issues-workflowAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 115 |
|---|---|
| repo stars | ★ 62 |
| Last updated | August 3, 2026 |
| Repository | terrylica/cc-skills ↗ |
What it does
Use issues-workflow for development tasks
Files
GitHub Issues-First Workflow
Default: Use GitHub Issues exclusively for all content, hierarchy, and tracking. Optional: Link to Projects v2 for cross-repo visualization only.
Self-Evolving Skill: This skill improves through use. If instructions are wrong, parameters drifted, or a workaround was needed — fix this file immediately, don't defer. Only update for real, reproducible issues.
Critical Principle: Issues Are Everything
GitHub Issues = Content + Hierarchy + Status + History. GitHub Projects v2 = Visualization layer only (no content, no history).
With sub-issues (GA April 2025), Issues now handle hierarchy natively. Projects v2 is reduced to an optional visualization dashboard.
Issues vs Projects v2
| Capability | Issues (Default) | Projects v2 (Visualization Only) |
|---|---|---|
| Content | Body, comments, code blocks | None (links to Issues only) |
| Hierarchy | Sub-issues (100 per parent) | Flat list |
| Status | Open/Closed + labels | Custom fields (no history) |
| Edit history | Full diff on every edit | None |
| Timeline | All changes logged | Status changes only (30-day limit) |
| Search | Full-text + 30+ filters | Limited |
| CLI | gh issue list/view/create | gh project (Classic PAT only) |
| Cross-repo | Manual (--repo A --repo B) | Single dashboard view |
When to Use Each
┌─────────────────────────────────────────────────────────────┐
│ ISSUES-FIRST WORKFLOW │
├─────────────────────────────────────────────────────────────┤
│ │
│ ALWAYS use Issues for: │
│ ├── All content (findings, analysis, conclusions) │
│ ├── Hierarchy (parent + sub-issues) │
│ ├── Status tracking (labels: status:in-progress) │
│ ├── Categorization (labels: research:regime, priority:P0) │
│ └── Filtering (gh issue list --label X --state Y) │
│ │
│ OPTIONALLY use Projects v2 for: │
│ ├── Cross-repo dashboard (single view across repos) │
│ ├── Kanban visualization (drag-and-drop board) │
│ ├── Roadmap timeline (visual date-based view) │
│ └── Stakeholder reporting (Status Updates feed) │
│ │
│ NEVER put in Projects v2: │
│ ├── Research findings (no edit history) │
│ ├── Analysis details (lost on update) │
│ ├── Any text content (use Issue body/comments) │
│ └── Anything you need to track changes for │
│ │
└─────────────────────────────────────────────────────────────┘Decision Tree
Need to track work?
├── Single repo, <50 issues → Issues only (skip Projects)
├── Single repo, 50+ issues → Issues + optional Project for kanban
├── Multiple repos → Issues + Project for cross-repo dashboard
└── Stakeholder visibility → Issues + Project Status UpdatesWhen to Use This Skill
Use this skill when:
- Setting up issue hierarchy with sub-issues (default workflow)
- Creating cross-repo visualization dashboards
- Configuring auto-linking from Issues to Projects
- Setting up stakeholder Status Updates
Remember: All content lives in Issues. Projects v2 is a read-only visualization layer.
Invocation
Slash command: /gh-tools:issues-workflow
Natural language triggers:
- "Create sub-issues for this parent"
- "Set up issue hierarchy"
- "Create cross-repo dashboard"
- "Link issues to project for visualization"
Issues-First Workflow (Default)
Sub-Issues: Native Hierarchy (GA April 2025)
Sub-issues replace the need for Projects v2 hierarchy. Use for all structured work.
When to Use Sub-Issues
| Use Case | Example | Why Sub-Issues |
|---|---|---|
| Research breakdown | Parent: "Investigate microstructure" → Subs: individual patterns | Track which patterns validated/invalidated |
| Epic decomposition | Parent: "User authentication" → Subs: login, logout, password reset | Progress bar shows completion % |
| Multi-step investigation | Parent: "Debug performance issue" → Subs: profiling, memory, CPU | Each sub can be assigned differently |
| Phased work | Parent: "v2.0 release" → Subs: Phase 1, Phase 2, Phase 3 | Natural ordering with timeline |
When NOT to Use Sub-Issues
| Situation | Use Instead | Why |
|---|---|---|
| Simple checklist (< 5 items) | Markdown checkboxes in issue body | Less overhead, editable inline |
| Cross-repo dependencies | Issue references (See org/repo#123) | Sub-issues are same-repo only |
| Loose relationships | "Related to #X" in body | Sub-issues imply containment |
| One-off tasks | Single issue with labels | Don't over-structure |
Creating Sub-Issues
# Create parent issue
gh issue create --title "Research: Range Bar Microstructure" \
--label "research:parent" --repo terrylica/rangebar-py
# Create sub-issues (reference parent in body or use UI)
gh issue create --title "Regime detection patterns" \
--body "Parent: #100" --label "research:sub" --repo terrylica/rangebar-pyStructure example:
#100 Research: Range Bar Microstructure (parent)
├── #101 Regime detection patterns - Invalidated ✗
├── #102 Cross-threshold correlations - Validated ✓
├── #103 Duration normalization - In Progress
└── #104 Microstructure features v7.0 - OpenSub-Issue Features
- Progress bar: Parent shows "X of Y completed" with visual bar
- Bidirectional links: Sub shows "Parent issue" in sidebar, parent lists all subs
- Automatic tracking: Close sub → parent progress updates
- Nesting: Up to 8 levels deep (sub-sub-sub-issues)
- Limit: 100 sub-issues per parent
Migration Note: Tasklist blocks retired April 30, 2025. Sub-issues are the official replacement. No migration tooling - manual conversion required.
Status via Labels (No Projects Needed)
Use labels instead of Project custom fields:
| Label Pattern | Purpose | Example |
|---|---|---|
status:* | Workflow state | status:in-progress |
priority:* | Urgency | priority:P0 |
research:* | Research categorization | research:validated |
type:* | Issue classification | type:hypothesis |
# Filter by status
gh issue list --label "status:in-progress" --repo terrylica/rangebar-py
# Filter by research outcome
gh issue list --label "research:validated" --state all
# Combined filters
gh issue list --label "research:regime,status:complete" --state closedIssue Types (GA 2025)
Organization-level standardization (orgs only, not personal accounts):
# Configure at: Organization Settings → Issues → Issue Types
# Personal accounts: Use labels instead (type:hypothesis, type:finding)Projects v2: Visualization Layer (Optional)
Use Projects v2 only for cross-repo visualization. All content remains in Issues.
When to Use Projects v2
| Use Case | Why Projects v2 Helps |
|---|---|
| Cross-repo dashboard | Single view across multiple repos |
| Kanban board | Drag-and-drop visual workflow |
| Roadmap timeline | Date-based visual planning |
| Stakeholder Status | Status Updates feed (ON_TRACK, etc.) |
When NOT to Use Projects v2
- Single repo with < 50 issues (use
gh issue listfilters) - Need edit history (Projects has none)
- Need content storage (use Issue body/comments)
- Need version tracking (Projects loses previous values)
Auto-Linking Issues to Projects
Link Issues automatically so Projects stay in sync:
Option 1: Label prefix convention
| Label | Auto-links to |
|---|---|
project:research | Research Findings |
project:dev | Active Development |
Option 2: Config file (.github/project-links.json):
{
"mappings": [
{
"labels": ["research:regime", "research:validated"],
"projectNumber": 2
}
],
"owner": "terrylica"
}Status Updates (Stakeholder Communication)
# Create status update via GraphQL
gh api graphql -f query='
mutation($projectId: ID!, $body: String!, $status: ProjectV2StatusUpdateStatus!) {
createProjectV2StatusUpdate(input: {
projectId: $projectId
body: $body
startDate: "2026-02-01"
status: $status
}) {
statusUpdate { id status body }
}
}' -f projectId="PVT_xxx" -f body="Research phase complete" -f status="ON_TRACK"
# Status values: ON_TRACK | AT_RISK | OFF_TRACK | COMPLETE | INACTIVEToken Requirements
CRITICAL: Projects v2 API requires Classic PAT with project scope.
# Check token type
cat ~/.claude/.secrets/gh-token-terrylica | head -c 10
# ghp_ = Classic PAT (supports Projects)
# github_pat_ = Fine-grained (NO Projects support)Project Commands Reference
# List/create/view projects
gh project list --owner <owner>
gh project create --owner <owner> --title "Dashboard Name"
gh project view <number> --owner <owner>
# Link issues to project (for visualization)
gh project item-add <project-number> --owner <owner> \
--url https://github.com/<owner>/<repo>/issues/<number>
# Bulk link by label
gh issue list --repo terrylica/rangebar-py \
--label "research:regime" --json url --jq '.[].url' | \
while read url; do
gh project item-add 2 --owner terrylica --url "$url"
doneGitHub Issues: Complete Feature Reference
Core Issue Commands
# Create issue
gh issue create --title "Title" --body "Body" --label "label1,label2"
# Create with body file (alternative for very long content)
gh issue create --title "Title" --body-file /tmp/issue-body.md
# View issue
gh issue view <number> --repo owner/repo
# List with filters
gh issue list --label "research:validated" --state all --assignee @me
# Edit issue
gh issue edit <number> --add-label "status:complete" --remove-label "status:in-progress"
# Close/reopen
gh issue close <number> --reason completed
gh issue reopen <number>Advanced Filtering (30+ qualifiers)
# By multiple labels (AND logic)
gh issue list --label "research:regime,priority:P0"
# By milestone
gh issue list --milestone "Research Phase 1"
# By date
gh issue list --search "created:>2025-01-01 updated:<2025-12-01"
# By author/assignee
gh issue list --author @me --assignee username
# Full-text search
gh issue list --search "microstructure in:title,body"
# Combine everything
gh issue list \
--label "research:validated" \
--state closed \
--search "regime created:>2025-06-01" \
--json number,title,labelsIssue Relationships
# Reference in body (creates link in timeline)
"Related to #45"
"Closes #123"
"Fixes #456"
# Cross-repo reference
"See terrylica/other-repo#789"
# Sub-issue (parent reference in body)
"Parent: #100"Timeline and History
# View full timeline (all events)
gh api repos/owner/repo/issues/123/timeline --paginate
# View edit history (via web UI or API)
gh api repos/owner/repo/issues/123 --jq '.body_html'
# Comment with preserved history
gh issue comment <number> --body "Update: new findings"Milestones (Alternative to Project Iterations)
# List milestones
gh api repos/owner/repo/milestones
# Create milestone
gh api repos/owner/repo/milestones -f title="Research Phase 2" -f due_on="2026-03-01"
# Assign issue to milestone
gh issue edit <number> --milestone "Research Phase 2"Workflow Examples
Issues-Only Research Workflow
# 1. Create parent research issue
gh issue create \
--title "Research: Range Bar Microstructure Patterns" \
--label "research:parent,priority:P1" \
--body-file /tmp/research-parent.md
# 2. Create sub-issues for each investigation
for topic in "regime-detection" "cross-threshold" "duration-normalization"; do
gh issue create \
--title "Sub: $topic analysis" \
--label "research:sub" \
--body "Parent: #100"
done
# 3. Track progress via labels
gh issue edit 101 --add-label "research:invalidated"
gh issue edit 102 --add-label "research:validated"
gh issue close 101 --reason "not planned"
# 4. Filter to see status
gh issue list --label "research:sub" --state all --json number,title,state,labelsOptional: Add to Project for Visualization
# Only if you need cross-repo dashboard
gh issue list --label "research:validated" --json url --jq '.[].url' | \
while read url; do
gh project item-add 2 --owner terrylica --url "$url"
doneTitle Evolution: Re-evaluate on New Comments
PRINCIPLE: GitHub issue titles have a 256-character limit. Maximize this limit to create informative titles that reflect the current state of the issue.
When to Re-evaluate
Re-evaluate and potentially update the issue title when significant new information is added - new findings, status changes, scope expansion, or when the journey is complete.
Commands
# Check current title length
gh issue view <number> --json title --jq '.title | length'
# Update title (maximize 256 chars based on content)
gh issue edit <number> --title "..."The AI agent determines the best way to maximize informativeness based on the nature of the content.
Issue-Branch-PR Lifecycle
The full lifecycle from issue to merged code, with automatic issue closure. All automation is local — no GitHub Actions for testing/linting.
Recommended Workflow
1. Create issue(s) → gh issue create --title "..." --body "..."
2. Branch from issue → gh issue develop <N> --checkout
3. Implement + commit → git commit -m "feat: description"
4. Create PR (with keywords) → gh pr create --body "Closes #N"
5. Merge PR → gh pr merge --squash --delete-branch
6. Issue auto-closes → GitHub handles this automaticallyClosing Keywords (Auto-Close on Merge)
Use Closes #N, Fixes #N, or Resolves #N in PR body (not title) to auto-close issues on merge. Case-insensitive. Cross-repo: Closes owner/repo#N. Each issue needs its own keyword — Closes #1, closes #2, fixes #3.
Branch-from-Issue (gh issue develop)
gh issue develop 214 --checkout # auto-names branch
gh issue develop 214 --name feat/x --checkout # custom nameCreates branch linked to issue. PRs from this branch auto-link and auto-close the issue on merge.
Use both develop AND closing keywords — belt-and-suspenders.
Full reference: Issue-Branch Lifecycle Details — keyword placement rules, cross-repo closing, bulk closure, branch cleanup
GFM Rendering Anti-Patterns
NEVER use bare `#N` in issue/PR comments. GitHub auto-links any #N where issue N exists — in prose, tables, lists. This is unpredictable and inconsistent (some numbers link, others don't).
- To reference an issue: Use explicit full URL →
[Issue 13](https://github.com/owner/repo/issues/13) - For non-issue numbers: Suppress with backtick → `
#1` - Backslash `\#1` does NOT work inside table cells
See the full reference for 6 documented anti-patterns: [GFM Anti-Patterns Reference](./references/gfm-antipatterns.md)
Troubleshooting
| Issue | Cause | Fix |
|---|---|---|
#N auto-links in tables | GFM auto-reference | Use backtick code span: ` #1 ` (details) |
| "Resource not accessible" | Fine-grained PAT | Use Classic PAT for Projects v2 |
| Sub-issues not linking | Wrong body format | Use exact "Parent: #123" syntax |
| Labels not filtering correctly | Typo in label name | gh label list to verify exact names |
| Long body truncated | GitHub 65536-char API limit | Shorten content or split across comments |
| Title too short/vague | Not using full limit | Maximize 256-char limit for context |
References
- gh-tools Issue Create Skill
- Issue-Branch Lifecycle — Closing keywords,
gh issue develop, local-first automation - GFM Anti-Patterns
- Field Types Reference
- Auto-Link Configuration
- GraphQL Queries Reference
- GitHub Issues Documentation
Post-Execution Reflection
After this skill completes, check before closing:
1. Did the command succeed? — If not, fix the instruction or error table that caused the failure. 2. Did parameters or output change? — If the underlying tool's interface drifted, update Usage examples and Parameters table to match. 3. Was a workaround needed? — If you had to improvise (different flags, extra steps), update this SKILL.md so the next invocation doesn't need the same workaround.
Only update if the issue is real and reproducible — not speculative.
Auto-Link Configuration Reference
Automatically link issues to projects based on labels, milestones, or repository.
Configuration File
Create .github/project-links.json in your repository:
{
"version": "1.0",
"owner": "terrylica",
"mappings": [
{
"name": "Research Issues",
"projectNumber": 2,
"projectTitle": "Research Findings: Range Bar Patterns",
"triggers": {
"labels": [
"research:regime",
"research:patterns",
"research:complete",
"negative-finding"
],
"labelPrefixes": ["research:"],
"milestones": ["Research Phase 1", "Research Phase 2"]
},
"defaultFields": {
"Status": "Todo"
}
},
{
"name": "Active Development",
"projectNumber": 3,
"projectTitle": "rangebar-py: Active Development",
"triggers": {
"labels": ["bug", "enhancement"],
"repositories": ["terrylica/rangebar-py"]
},
"defaultFields": {
"Status": "Todo",
"Priority": "P2 Medium"
}
},
{
"name": "Plugin Development",
"projectNumber": 4,
"projectTitle": "cc-skills: Plugin Development",
"triggers": {
"repositories": ["terrylica/cc-skills"],
"labels": ["gh-tools", "new-skill", "enhancement"]
}
}
],
"settings": {
"skipDuplicates": true,
"logFile": "~/.claude/logs/project-autolink.jsonl"
}
}Trigger Types
Label-Based
Match issues with specific labels:
{
"triggers": {
"labels": ["bug", "enhancement", "documentation"]
}
}Label Prefix
Match issues with labels starting with a prefix:
{
"triggers": {
"labelPrefixes": ["research:", "project:", "team:"]
}
}Milestone-Based
Match issues assigned to specific milestones:
{
"triggers": {
"milestones": ["v1.0", "Q1 2026", "Research Phase 1"]
}
}Repository-Based
Match all issues from specific repositories:
{
"triggers": {
"repositories": ["terrylica/rangebar-py", "terrylica/cc-skills"]
}
}Combined Triggers (AND Logic)
All conditions must match:
{
"triggers": {
"labels": ["bug"],
"repositories": ["terrylica/rangebar-py"]
},
"requireAll": true
}Combined Triggers (OR Logic)
Any condition matches (default):
{
"triggers": {
"labels": ["bug", "enhancement"],
"milestones": ["v1.0"]
},
"requireAll": false
}Default Field Values
Set initial field values when linking:
{
"defaultFields": {
"Status": "Todo",
"Priority": "P2 Medium",
"Iteration": "Current Sprint"
}
}Label Convention Pattern
Use labels with project: prefix for self-documenting auto-linking:
| Label | Auto-links to |
|---|---|
project:research | Project #2 (Research Findings) |
project:dev | Project #3 (Active Development) |
project:plugins | Project #4 (Plugin Development) |
Hook Implementation
PostToolUse Hook (TypeScript)
// ~/.claude/hooks/posttooluse-project-autolink.ts
import { execSync } from "child_process";
import { readFileSync, existsSync } from "fs";
interface ProjectMapping {
projectNumber: number;
triggers: {
labels?: string[];
labelPrefixes?: string[];
milestones?: string[];
repositories?: string[];
};
defaultFields?: Record<string, string>;
}
interface Config {
owner: string;
mappings: ProjectMapping[];
}
export function onPostToolUse(tool: string, output: string, command: string) {
if (tool !== "Bash") return;
if (!output.includes("github.com") || !output.includes("/issues/")) return;
// Extract issue URL from gh issue create output
const urlMatch = output.match(
/https:\/\/github\.com\/([^\/]+)\/([^\/]+)\/issues\/(\d+)/,
);
if (!urlMatch) return;
const [, owner, repo, issueNum] = urlMatch;
const issueUrl = urlMatch[0];
// Load config
const configPath = `${process.cwd()}/.github/project-links.json`;
if (!existsSync(configPath)) return;
const config: Config = JSON.parse(readFileSync(configPath, "utf-8"));
// Get issue labels
const labelsOutput = execSync(
`gh issue view ${issueNum} --repo ${owner}/${repo} --json labels --jq '.labels[].name'`,
{ encoding: "utf-8" },
);
const labels = labelsOutput.trim().split("\n").filter(Boolean);
// Find matching projects
for (const mapping of config.mappings) {
const matches = checkTriggers(mapping.triggers, labels, `${owner}/${repo}`);
if (matches) {
execSync(
`gh project item-add ${mapping.projectNumber} --owner ${config.owner} --url ${issueUrl}`,
);
console.log(`Linked issue to project #${mapping.projectNumber}`);
}
}
}
function checkTriggers(
triggers: ProjectMapping["triggers"],
labels: string[],
repo: string,
): boolean {
if (triggers.labels?.some((l) => labels.includes(l))) return true;
if (triggers.labelPrefixes?.some((p) => labels.some((l) => l.startsWith(p))))
return true;
if (triggers.repositories?.includes(repo)) return true;
return false;
}Manual Bulk Linking
Link existing issues to projects:
# Link all issues with research labels to project #2
gh issue list --repo terrylica/rangebar-py \
--label "research:regime" \
--state all \
--json url \
--jq '.[].url' | \
while read url; do
gh project item-add 2 --owner terrylica --url "$url"
echo "Linked: $url"
doneVerification
Check which issues are linked to a project:
gh project item-list 2 --owner terrylica --format json | \
jq '.items[] | {title: .title, url: .content.url, status: .status}'Related
- Field Types Reference
- GraphQL Queries Reference
Evolution Log
Convention: Reverse chronological order (newest on top, oldest at bottom). Prepend new entries.
---
2026-02-26: Initial Evolution Log
Status: Skill is in use and maintained. Track improvements here.
Purpose
This evolution log tracks updates to the skill. Each entry should note:
- What changed (content, structure, tooling)
- Why it changed (bug fix, feature request, best practice)
- Files affected
How to Use
1. When updating SKILL.md or references, add an entry here with the date 2. Keep entries reverse-chronological (newest first) 3. Link to ADRs or GitHub issues when relevant 4. Reference specific line changes when helpful
---
GitHub Projects v2 Field Types Reference
Complete reference for all field types available in GitHub Projects v2.
Built-in Fields (Read-Only)
These fields are automatically populated from the issue/PR:
| Field | Description |
|---|---|
| Title | Issue/PR title |
| Assignees | Assigned users |
| Labels | Applied labels |
| Milestone | Associated milestone |
| Repository | Source repository |
| Linked Pull Requests | PRs linked to issue |
| Reviewers | PR reviewers |
| Tracks/Tracked by | Parent/sub-issue relationships |
Custom Field Types
Single Select
Dropdown with predefined options. Each option has a name, description, and color.
Use cases: Status, Priority, Category, Type
CLI Creation:
gh project field-create <number> --owner <owner> \
--name "Priority" \
--data-type SINGLE_SELECTAdding Options (via GraphQL):
gh api graphql -f query='
mutation {
createProjectV2Field(input: {
projectId: "PROJECT_ID"
dataType: SINGLE_SELECT
name: "Priority"
singleSelectOptions: [
{name: "P0 Critical", color: RED, description: "Drop everything"},
{name: "P1 High", color: ORANGE, description: "This sprint"},
{name: "P2 Medium", color: YELLOW, description: "Next sprint"},
{name: "P3 Low", color: GREEN, description: "Backlog"}
]
}) {
projectV2Field { id name }
}
}'Available Colors: GRAY, RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE, PINK
Iteration
Time-boxed periods for sprint planning.
Use cases: Sprints, Releases, Quarters
CLI Creation:
gh project field-create <number> --owner <owner> \
--name "Sprint" \
--data-type ITERATIONFeatures:
- Configurable iteration duration (1-4 weeks)
- Support for breaks between iterations
- Automatic date calculations
- Velocity tracking across iterations
Date
Calendar date picker.
Use cases: Due Date, Start Date, Target Date, Review Date
CLI Creation:
gh project field-create <number> --owner <owner> \
--name "Due Date" \
--data-type DATEText
Free-form text field.
Use cases: Notes, Root Cause, Summary, Links
CLI Creation:
gh project field-create <number> --owner <owner> \
--name "Notes" \
--data-type TEXTNumber
Numeric field for quantitative data.
Use cases: Story Points, Estimate Hours, T-shirt Size (as numbers)
CLI Creation:
gh project field-create <number> --owner <owner> \
--name "Story Points" \
--data-type NUMBERResearch Project Field Templates
Research-Approach Field
# Options for categorizing research methodology
# TDA, Microstructure, Cross-threshold, Duration, Regime,
# Cross-asset, Pattern, Autocorrelation, Velocity, OtherVerdict Field
# Research outcome classification
# Validated - Pattern confirmed with statistical significance
# Invalidated - Pattern failed validation criteria
# Inconclusive - Insufficient data or mixed results
# Blocked - Technical/data issues prevented completionCommon Research Fields Combo
PROJECT_NUM=2
OWNER=terrylica
# Research-Approach
gh project field-create $PROJECT_NUM --owner $OWNER \
--name "Research-Approach" --data-type SINGLE_SELECT
# Verdict
gh project field-create $PROJECT_NUM --owner $OWNER \
--name "Verdict" --data-type SINGLE_SELECT
# Invalidation-Cause
gh project field-create $PROJECT_NUM --owner $OWNER \
--name "Invalidation-Cause" --data-type TEXT
# Data-Coverage
gh project field-create $PROJECT_NUM --owner $OWNER \
--name "Data-Coverage" --data-type TEXTField Limits
- Maximum custom fields per project: 100
- Maximum options per Single Select: 50
- Maximum iterations: 100
- Field names: 1-256 characters
Related
- Auto-Link Configuration
- GraphQL Queries Reference
GFM Anti-Patterns in Issue Comments
GitHub Flavored Markdown (GFM) has auto-linking behaviors that silently transform issue/PR comment content in unexpected ways. This reference documents known anti-patterns and their fixes.
---
Foundational Principle: No Implicit References
NEVER rely on `#N` shorthand for GitHub issue/PR references. The #N notation causes two problems:
1. False positives: #1 in a trade number column auto-links to Issue 1 2. Inconsistency: #59 renders as plain text if Issue 59 doesn't exist, but #4 becomes a link if Issue 4 does — same column, different rendering
Rule: Always suppress #N auto-linking. If you need to reference a GitHub issue, use an explicit full URL.
<!-- BAD: implicit #N — auto-links unpredictably -->
See #13 for details.
Trade #1 had a TP exit.
<!-- GOOD: explicit URL for intentional issue references -->
See [Issue 13](https://github.com/owner/repo/issues/13) for details.
<!-- GOOD: backtick suppression for non-issue numbers -->
Trade `#1` had a TP exit.---
AP-01: #N Auto-Links Everywhere
Problem: GitHub auto-links ANY #N where issue/PR N exists in the repo — in prose, in tables, in list items. This is not limited to table cells.
Policy: NEVER write bare #N in issue/PR comments. Always do one of:
| Intent | Write This | Not This |
|---|---|---|
| Reference an issue | [Issue 13](https://github.com/owner/repo/issues/13) | #13 |
| Non-issue number | ` #1 ` | #1 |
| Numbered list item | Trade 1, Item 1 | #1 |
In tables (most common trap):
<!-- BAD: #1 and #4 become issue links, #59 stays plain text -->
| Trade | Exit |
| ----- | ---- |
| #1 | TP |
| #4 | SL |
| #59 | TIME |
<!-- GOOD: consistent rendering, no auto-links -->
| Trade | Exit |
| ----- | ---- |
| `#1` | TP |
| `#4` | SL |
| `#59` | TIME |What does NOT work inside table cells:
\#1— Backslash escaping is ignored#1— HTML entity still auto-links<span>#1</span>— Still auto-links
Only backtick code spans work: ` #1 `
---
AP-02: @username Auto-Mentions in Code Context
Problem: @username in plain text triggers a GitHub mention notification, even when discussing code (e.g., decorator syntax @property, email addresses).
Example:
The class uses @property for lazy loading. <!-- Pings user "property" if they exist -->
Contact admin@example.com for access. <!-- May ping user "example" -->Fix: Use backticks for code references, angle brackets for emails:
The class uses `@property` for lazy loading.
Contact <admin@example.com> for access.---
AP-03: SHA-Like Hex Strings Auto-Link to Commits
Problem: Strings that look like Git commit SHAs (7+ hex characters) auto-link to commits if a matching commit exists in the repository.
Example:
Error code: 0xDEADBEEF <!-- May link to a commit -->
Color value: #FF5733 <!-- Links if commit ff5733 exists -->Fix: Use backticks for hex values, code blocks for error output:
Error code: `0xDEADBEEF`
Color value: `#FF5733`---
AP-04: Shell Quoting Issues in --body Inline
Problem: Complex markdown with unescaped backticks, double quotes, and newlines can be mangled by shell quoting when passed via --body "...".
Fix: Use single-quoted heredoc for complex content:
# Fragile: double-quoted string with special characters
gh issue comment 13 --body "## Title\n\nBody with `code` and \"quotes\""
# Reliable: heredoc (no escaping needed)
gh issue comment 13 --body "$(cat <<'EOF'
## Title
Long body with `code` and "quotes" — no escaping needed.
EOF
)"
# Alternative: body file for very large content (65536-char GitHub API limit)
gh issue comment 13 --body-file /tmp/comment.md---
AP-05: Bare URLs vs Reference-Style Links
Problem: Bare URLs in issue bodies can break if they contain parentheses, query params, or markdown-special characters.
Example:
See https://example.com/path_(with_parens) <!-- Link breaks at first ) -->
See https://example.com/search?q=a&b=c#section <!-- May break at # or & -->Fix: Use angle brackets for complex URLs:
See <https://example.com/path_(with_parens)>
See <https://example.com/search?q=a&b=c#section>---
AP-06: Pipe Characters in Table Cell Code Blocks
Problem: | inside table cells breaks the table structure, even inside backticks in some edge cases.
Example:
| Command | Description |
| ------- | ----------- | ------------- | -------------------------------- |
| `a | b` | Pipe operator | <!-- May break table parsing --> |Fix: Use HTML entity | for literal pipes in tables:
| Command | Description |
| ------------ | ------------- |
| `a | b` | Pipe operator |---
AP-07: Private Repo Image URLs Render as Broken
Problem: Images committed to a private repository don't render in Issue/PR bodies when referenced via raw.githubusercontent.com. The browser fetches <img src> URLs directly — raw.githubusercontent.com is a different domain from github.com, so no session cookies are sent, and the image 404s silently.
Example:
<!-- BROKEN: raw.githubusercontent.com has no browser cookies for private repos -->

<!-- WORKING: github.com/blob/?raw=true uses the domain where browser IS authenticated -->
Why `?raw=true` works:
1. Browser requests github.com/...?raw=true — sends session cookies (same domain as the issue page) 2. GitHub validates repo access via cookies 3. GitHub responds with 302 redirect to raw.githubusercontent.com/...?token=SIGNED_TEMP_TOKEN 4. Browser follows redirect — signed token grants access without cookies 5. Image loads
Why `raw.githubusercontent.com` fails:
1. Browser requests raw.githubusercontent.com/... — no cookies (different domain) 2. Private repo content requires authentication 3. Request returns 404 4. Image renders as broken
Key rules:
| Repo Visibility | URL Format | Works? |
|---|---|---|
| Public | raw.githubusercontent.com/owner/repo/... | Yes |
| Public | github.com/owner/repo/blob/...?raw=true | Yes |
| Private | raw.githubusercontent.com/owner/repo/... | No |
| Private | github.com/owner/repo/blob/...?raw=true | Yes |
URL pattern (for scripting):
# Base URL for private repo images in Issues/PRs
IMG_BASE="https://github.com/${OWNER}/${REPO}/blob/${BRANCH}/${PATH_TO_DIR}"
# Reference in markdown
Note: This applies to any context where GitHub renders markdown and the browser loads images client-side — Issue bodies, Issue comments, PR descriptions, PR review comments, and Discussion posts. It does NOT affect README rendering (GitHub proxies those server-side).
Alternative: Upload to `user-attachments` CDN directly. Instead of committing images to the repo, Playwright can automate GitHub's file-attachment flow to get permanent CDN URLs (https://github.com/user-attachments/assets/UUID). These URLs work regardless of repo visibility and require no commit/push preflight. See Playwright Automation in issue-create for implementation details.
---
Quick Reference Card
| Anti-Pattern | Trigger | Policy | Fix |
|---|---|---|---|
| AP-01 | #N anywhere | NEVER use bare #N | Backtick ` #N ` for non-issues; explicit URL for real references |
| AP-02 | @name in prose | Suppress unintentional mentions | Backtick ` @property ` |
| AP-03 | Hex strings | Suppress commit auto-links | Backtick ` 0xDEAD ` |
| AP-04 | Complex --body | Use heredoc for special chars | Heredoc $(cat <<'EOF'...) or --body-file |
| AP-05 | Complex URLs | Protect special characters | Angle brackets <URL> |
| AP-06 | Pipe in table | Escape pipe character | HTML entity | |
| AP-07 | Private repo img | NEVER use raw.githubusercontent.com | github.com/.../blob/...?raw=true |
---
When to Apply
These anti-patterns apply to:
- Issue bodies and comments (
gh issue create,gh issue comment) - PR descriptions (
gh pr create) - Discussion posts
- Wiki pages
- Any GitHub-rendered Markdown
They do NOT apply to:
- Code blocks (triple backtick fenced blocks) — auto-linking is disabled inside these
- Repository file rendering (
.mdfiles in repos) — same rules apply but less commonly hit
GraphQL Queries Reference
Advanced GitHub Projects v2 operations via GraphQL API.
Authentication
All GraphQL queries require a Classic PAT with project scope:
export GH_TOKEN=$(cat ~/.claude/.secrets/gh-token-terrylica)Query: List User Projects
gh api graphql -f query='
query($login: String!) {
user(login: $login) {
projectsV2(first: 20) {
nodes {
id
number
title
url
closed
items { totalCount }
fields(first: 20) {
nodes {
... on ProjectV2Field { id name }
... on ProjectV2SingleSelectField { id name options { id name color } }
... on ProjectV2IterationField { id name }
}
}
}
}
}
}' -f login="terrylica"Query: Get Project Details
gh api graphql -f query='
query($login: String!, $number: Int!) {
user(login: $login) {
projectV2(number: $number) {
id
title
shortDescription
readme
url
items(first: 100) {
nodes {
id
content {
... on Issue {
number
title
state
url
labels(first: 10) { nodes { name } }
}
... on PullRequest {
number
title
state
url
}
}
fieldValues(first: 20) {
nodes {
... on ProjectV2ItemFieldTextValue { text field { ... on ProjectV2Field { name } } }
... on ProjectV2ItemFieldNumberValue { number field { ... on ProjectV2Field { name } } }
... on ProjectV2ItemFieldDateValue { date field { ... on ProjectV2Field { name } } }
... on ProjectV2ItemFieldSingleSelectValue {
name
optionId
field { ... on ProjectV2SingleSelectField { name } }
}
... on ProjectV2ItemFieldIterationValue {
title
startDate
duration
field { ... on ProjectV2IterationField { name } }
}
}
}
}
}
}
}
}' -f login="terrylica" -F number=2Query: Get Project Fields
gh api graphql -f query='
query($login: String!, $number: Int!) {
user(login: $login) {
projectV2(number: $number) {
fields(first: 50) {
nodes {
... on ProjectV2Field {
id
name
dataType
}
... on ProjectV2SingleSelectField {
id
name
dataType
options {
id
name
color
description
}
}
... on ProjectV2IterationField {
id
name
dataType
configuration {
iterations {
id
title
startDate
duration
}
}
}
}
}
}
}
}' -f login="terrylica" -F number=2Mutation: Create Project
gh api graphql -f query='
mutation($ownerId: ID!, $title: String!) {
createProjectV2(input: {
ownerId: $ownerId
title: $title
}) {
projectV2 {
id
number
url
}
}
}' -f ownerId="USER_NODE_ID" -f title="New Project"Get user node ID first:
gh api graphql -f query='query { viewer { id login } }'Mutation: Add Item to Project
gh api graphql -f query='
mutation($projectId: ID!, $contentId: ID!) {
addProjectV2ItemById(input: {
projectId: $projectId
contentId: $contentId
}) {
item {
id
}
}
}' -f projectId="PROJECT_NODE_ID" -f contentId="ISSUE_NODE_ID"Get issue node ID:
gh api graphql -f query='
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
issue(number: $number) {
id
title
}
}
}' -f owner="terrylica" -f repo="rangebar-py" -F number=57Mutation: Update Item Field Value
Single Select Field
gh api graphql -f query='
mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
updateProjectV2ItemFieldValue(input: {
projectId: $projectId
itemId: $itemId
fieldId: $fieldId
value: { singleSelectOptionId: $optionId }
}) {
projectV2Item { id }
}
}' \
-f projectId="PVT_xxx" \
-f itemId="PVTI_xxx" \
-f fieldId="PVTSSF_xxx" \
-f optionId="OPTION_ID"Text Field
gh api graphql -f query='
mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $text: String!) {
updateProjectV2ItemFieldValue(input: {
projectId: $projectId
itemId: $itemId
fieldId: $fieldId
value: { text: $text }
}) {
projectV2Item { id }
}
}' \
-f projectId="PVT_xxx" \
-f itemId="PVTI_xxx" \
-f fieldId="PVTF_xxx" \
-f text="Root cause: boundary-locked returns"Date Field
gh api graphql -f query='
mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $date: Date!) {
updateProjectV2ItemFieldValue(input: {
projectId: $projectId
itemId: $itemId
fieldId: $fieldId
value: { date: $date }
}) {
projectV2Item { id }
}
}' \
-f projectId="PVT_xxx" \
-f itemId="PVTI_xxx" \
-f fieldId="PVTF_xxx" \
-f date="2026-02-15"Mutation: Create Custom Field
Single Select Field with Options
gh api graphql -f query='
mutation($projectId: ID!, $name: String!) {
createProjectV2Field(input: {
projectId: $projectId
dataType: SINGLE_SELECT
name: $name
singleSelectOptions: [
{name: "Validated", color: GREEN, description: "Pattern confirmed"},
{name: "Invalidated", color: RED, description: "Pattern failed"},
{name: "Inconclusive", color: YELLOW, description: "Mixed results"},
{name: "Blocked", color: GRAY, description: "Technical issues"}
]
}) {
projectV2Field {
... on ProjectV2SingleSelectField {
id
name
options { id name color }
}
}
}
}' -f projectId="PVT_xxx" -f name="Verdict"Text Field
gh api graphql -f query='
mutation($projectId: ID!, $name: String!) {
createProjectV2Field(input: {
projectId: $projectId
dataType: TEXT
name: $name
}) {
projectV2Field {
... on ProjectV2Field { id name }
}
}
}' -f projectId="PVT_xxx" -f name="Invalidation-Cause"Mutation: Delete Item from Project
gh api graphql -f query='
mutation($projectId: ID!, $itemId: ID!) {
deleteProjectV2Item(input: {
projectId: $projectId
itemId: $itemId
}) {
deletedItemId
}
}' -f projectId="PVT_xxx" -f itemId="PVTI_xxx"Helper: Get All IDs for a Project
# Get project ID and field IDs
gh api graphql -f query='
query($login: String!, $number: Int!) {
user(login: $login) {
projectV2(number: $number) {
id
title
fields(first: 30) {
nodes {
... on ProjectV2Field { id name dataType }
... on ProjectV2SingleSelectField {
id name dataType
options { id name }
}
}
}
}
}
}' -f login="terrylica" -F number=2 | jq '.'2025 Mutations
Status Updates (June 2024+)
Create project status updates for stakeholder communication:
gh api graphql -f query='
mutation($projectId: ID!, $body: String!, $startDate: Date!, $status: ProjectV2StatusUpdateStatus!) {
createProjectV2StatusUpdate(input: {
projectId: $projectId
body: $body
startDate: $startDate
status: $status
}) {
statusUpdate {
id
body
status
startDate
targetDate
createdAt
}
}
}' \
-f projectId="PVT_xxx" \
-f body="Sprint 3 research complete - all hypotheses tested" \
-f startDate="2026-02-01" \
-f status="ON_TRACK"Status enum: ON_TRACK | AT_RISK | OFF_TRACK | COMPLETE | INACTIVE
Update Status Update
gh api graphql -f query='
mutation($statusUpdateId: ID!, $body: String, $status: ProjectV2StatusUpdateStatus) {
updateProjectV2StatusUpdate(input: {
statusUpdateId: $statusUpdateId
body: $body
status: $status
}) {
statusUpdate { id status body }
}
}' -f statusUpdateId="PVTSU_xxx" -f status="COMPLETE"Convert Draft to Issue
Convert draft issues to real issues (with full version tracking):
gh api graphql -f query='
mutation($projectId: ID!, $itemId: ID!, $repositoryId: ID!) {
convertProjectV2DraftIssueItemToIssue(input: {
projectId: $projectId
itemId: $itemId
repositoryId: $repositoryId
}) {
item {
id
content {
... on Issue { number title url }
}
}
}
}' \
-f projectId="PVT_xxx" \
-f itemId="PVTI_xxx" \
-f repositoryId="R_xxx"Note: Convert drafts to Issues promptly - drafts lack labels, milestones, notifications, and version history.
Add Draft Issue
Quick capture before converting to real Issue:
gh api graphql -f query='
mutation($projectId: ID!, $title: String!, $body: String) {
addProjectV2DraftIssue(input: {
projectId: $projectId
title: $title
body: $body
}) {
projectItem { id }
}
}' \
-f projectId="PVT_xxx" \
-f title="Research idea: cross-asset correlation" \
-f body="Initial hypothesis to explore"Error Handling
Common errors and solutions:
| Error | Cause | Solution |
|---|---|---|
NOT_FOUND | Wrong project/item ID | Verify IDs with query first |
FORBIDDEN | Missing project scope | Use Classic PAT |
UNPROCESSABLE | Invalid field value | Check field type and options |
RESOURCE_LIMITS_EXCEEDED | Query too complex | Reduce nesting depth or pagination |
API Limits
| Limit | Value | Notes |
|---|---|---|
| Points per hour | 5,000 (10,000 with GitHub App) | Monitor with X-RateLimit-* headers |
| Concurrent requests | 100 | Per authenticated user |
| Node limit per query | 500,000 | Reduce first: values if exceeded |
| Timeline retention | 30 days | For Events API access |
Related
- Field Types Reference
- Auto-Link Configuration
Issue-Branch-PR Lifecycle Reference
Parent: Issues Workflow SKILL.md
Closing Keywords — Complete Specification
GitHub recognizes these keywords in PR bodies and commit messages. On merge to the default branch, referenced issues are automatically closed.
Supported Keywords
All keywords are case-insensitive and work with #N or owner/repo#N syntax:
close,closes,closedfix,fixes,fixedresolve,resolves,resolved
Placement Rules
| Location | Auto-closes? | Notes |
|---|---|---|
| PR body | Yes | Most reliable — always use this |
| Commit message | Yes | Only when merged to default branch |
| PR title | No | Never triggers auto-close |
| PR comment | No | Never triggers auto-close |
| Issue body/comment | No | Creates a reference link only |
Multiple Issues
Closes #1, closes #2, fixes #3Each issue needs its own keyword. Closes #1, #2, #3 only closes #1.
Cross-Repo Closing
Closes terrylica/other-repo#42Requires push access to the target repository.
gh issue develop — Branch-from-Issue
Creates a branch linked to an issue with automatic PR-issue association.
Command Reference
# Basic (auto-names branch from issue title)
gh issue develop <number>
# With checkout
gh issue develop <number> --checkout
# Custom branch name
gh issue develop <number> --name <branch-name> --checkout
# Specify base branch
gh issue develop <number> --base main --checkoutWhat Happens
1. Creates branch on remote (and optionally checks out locally) 2. Links branch to issue (visible in issue sidebar → "Development") 3. PRs from this branch auto-link to the issue 4. Merging the PR auto-closes the issue
Comparison: develop vs Closing Keywords
| Feature | gh issue develop | Closing keywords |
|---|---|---|
| Branch naming | Auto from issue title | Manual |
| Issue-PR link | Automatic | Automatic |
| Cross-repo | Same repo only | Cross-repo with push |
| Multiple issues | One branch per issue | One PR closes many issues |
| Requires gh CLI | Yes | No (just text in body) |
Recommendation: Use both. gh issue develop for branch creation, closing keywords in PR body for explicitness.
Local-First Automation Policy
No GitHub Actions for testing or linting. All quality gates run locally:
mise run check-full(fmt + lint + test + deny)cargo nextest run(Rust tests)pytest(Python tests)ruff check/clippy(linting)
GitHub Actions are reserved for: semantic-release, CodeQL, Dependabot, deployment.
See: GitHub Actions ADR
Complete Workflow Example
# 1. Create parent issue with sub-issues
gh issue create --title "feat: consumer API for flowsurface" \
--body "Parent tracker for consumer-facing API.
Sub-issues:
- [ ] Forming bar push (#214)
- [ ] Checkpoint push (#215)
- [ ] Gap-fill endpoint (#216)
- [ ] Ariadne endpoint (#217)" \
--label "type:epic"
# 2. Branch from parent issue
gh issue develop 213 --name feat/consumer-api --checkout
# 3. Implement across multiple commits
git add -A && git commit -m "feat: forming bar push via SSE (#214)"
git add -A && git commit -m "feat: checkpoint push (#215)"
# 4. Create PR with bulk auto-close
gh pr create --title "feat: consumer API for flowsurface (#213)" \
--body "Closes #213
Closes #214
Closes #215
Closes #216
Closes #217"
# 5. Squash merge + delete branch
gh pr merge --squash --delete-branch
# 6. All 5 issues auto-close on merge
# 7. Prune local stale branches
git fetch --prune
git branch -vv | grep ': gone]' | awk '{print $1}' | xargs git branch -d