triage

Validates all open GitHub issues, closes invalid ones with documentation, flags complex ones for planning discussion, then fixes and promotes remaining issues. Use when the user says triage issues, validate issues, fix issues, or work through the backlog.

1 Tools
claude-workflow-skills Plugin
productivity Category

Allowed Tools

Read Glob Grep Bash Edit Write

Provided by Plugin

claude-workflow-skills

Common workflow skills for Claude Code sessions: promote changes through the full release cycle, audit Claude Code plugins/skills/agents, audit project standards compliance, analyse projects for improvements, triage open GitHub issues, and review pull requests

productivity v1.7.1
View Plugin

Installation

This skill is included in the claude-workflow-skills plugin:

/plugin install claude-workflow-skills@claude-code-plugins-plus

Click to copy

Instructions

Triage

Project: !basename $(git rev-parse --show-toplevel 2>/dev/null) 2>/dev/null || basename $PWD

Branch: !git branch --show-current 2>/dev/null || echo "unknown"

Open issues: !gh issue list --state open --json number,title --jq '.[] | "#\(.number) \(.title)"' 2>/dev/null || echo "none"

Last tag: !git describe --tags --abbrev=0 2>/dev/null || echo "none"

Recent commits: !git log --oneline -5 2>/dev/null || echo "none"

Validates every open GitHub issue, closes those that are invalid, surfaces complex ones

for planning discussion, then fixes and promotes all remaining actionable issues.

Step 0: Pre-flight check


gh auth status 2>&1 || { echo "ERROR: gh is not authenticated. Run: gh auth login"; exit 1; }

Step 1: Fetch all open issues


gh issue list --state open --limit 100 --json number,title,body,labels,createdAt \
  --jq '.[] | {number, title, labels: [.labels[].name], createdAt, body}'

Read the full body of any issues where context is needed:


gh issue view <number> --json number,title,body,labels,comments

Also read the project files to understand current state before assessing anything:

  • README.md — stated purpose and feature set
  • CLAUDE.md — current status, known decisions, next steps

Step 2: Classify each issue

For every open issue, classify it as one of:

  • Invalid — already fixed, a duplicate, not reproducible, out of scope, or based on a

misunderstanding. Evidence must be clear.

  • Complex — valid but requires significant design decisions, breaking changes, or

multi-step implementation that warrants planning discussion before proceeding.

  • Actionable — valid, well-scoped, and fixable now without further input.

Build a classification table before taking any action:

# Title Classification Reason
N ... Invalid/Complex/Actionable ...

Step 3: Close invalid issues

For each invalid issue, post a closing comment that explains why it is being closed, then close it:


gh issue comment <number> --body "$(cat <<'EOF'
Closing as invalid: <specific reason — already fixed in <commit/PR>, duplicate of #N,
not reproducible because <evidence>, or out of scope because <reason>>.

<If already fixed: reference the commit or PR that resolved it.>
<If duplicate: reference the canonical issue.>
EOF
)"

gh issue close <number> --reason "not planned"

Use --reason "completed" if the issue was already resolved in code.

Step 4: Surface complex issues for planning

Stop and present complex issues to the user before proceeding. Format clearly:


Complex issues — planning needed before proceeding:

For each complex issue:

  • #N: </strong></li> <li>Why it's complex: <design decision, breaking change, or scope concern></li> <li>Options to consider: <brief list of approaches></li> <li>Suggested next step: defer to backlog / spike / discuss now</li> </ul> <hr> <p>Wait for the user's direction on each complex issue before moving to Step 5.</p> <p>If running non-interactively (no terminal), stop here and output the complex issues report.</p> <pre data-lang="bash"><code> if [ ! -t 0 ]; then echo "Non-interactive mode: stopping after complex issue report. Review and re-run interactively." exit 0 fi </code></pre> <h2>Step 5: Fix actionable issues</h2> <p>Work through each actionable issue. For each one:</p> <ol> <li>Read the relevant source files before making any changes.</li> <li>Implement the fix — prefer the minimal correct change; don't refactor beyond the scope of</li> </ol> <p>the issue.</p> <ol> <li>Verify the fix with any available linting or syntax checks:</li> </ol> <pre data-lang="bash"><code> bash -n <script.sh> # syntax check for shell scripts markdownlint '**/*.md' # markdown linting </code></pre> <ol> <li>Stage the change:</li> </ol> <pre data-lang="bash"><code> git add -u </code></pre> <p>Record each fix with its issue number so the commit and PR can reference them.</p> <h2>Step 6: Promote</h2> <p>Once all actionable issues are fixed, run:</p> <pre data-lang="text"><code> /promote </code></pre> <p>The promote skill will commit, push, create and merge a PR (referencing fixed issue numbers</p> <p>via <code>Closes #N</code> in the commit message), tag a release, and clean up.</p> <p>When composing the commit message for the promote step, include <code>Closes #N</code> for every</p> <p>issue fixed in Step 5.</p></div> </div> </section> <!-- 8. Bottom CTA --> <section class="detail-cta" data-astro-cid-qcigfm7x> <h2 class="detail-cta__headline" data-astro-cid-qcigfm7x>Ready to use claude-workflow-skills?</h2> <div class="detail-cta__actions" data-astro-cid-qcigfm7x> <button class="detail-cta__primary" data-copy-install="/plugin install claude-workflow-skills@claude-code-plugins-plus" data-astro-cid-qcigfm7x> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" data-astro-cid-qcigfm7x> <rect x="5" y="5" width="9" height="9" rx="1.5" stroke="currentColor" stroke-width="1.5" data-astro-cid-qcigfm7x></rect> <path d="M3 11V3a1 1 0 011-1h8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" data-astro-cid-qcigfm7x></path> </svg> <span class="detail-cta__btn-text" data-astro-cid-qcigfm7x>Copy Install Command</span> </button> </div> </section> <script type="module">document.querySelectorAll("[data-copy-install]").forEach(t=>{t.addEventListener("click",async()=>{const a=t.dataset.copyInstall||"";await navigator.clipboard.writeText(a);const e=t.querySelector(".detail-cta__btn-text");if(e){const o=e.textContent;e.textContent="Copied!",t.classList.add("copied"),window.trackEvent&&window.trackEvent("install_copied",{plugin_name:a.split(" ").pop()||"",source:"detail_cta"}),setTimeout(()=>{e.textContent=o,t.classList.remove("copied")},2e3)}})});</script> <!-- 9. Related Skills (6, with tool pills) --> <section class="related-skills-section" data-astro-cid-jrlgpo3w> <h2 class="related-skills-heading" data-astro-cid-jrlgpo3w>Related Skills</h2> <div class="related-skills-grid" data-astro-cid-jrlgpo3w> <a href="/skills/000-jeremy-content-consistency-validator/" class="related-skill-card" data-astro-cid-jrlgpo3w> <h3 class="related-skill-title" data-astro-cid-jrlgpo3w>000-jeremy-content-consistency-validator</h3> <p class="related-skill-description" data-astro-cid-jrlgpo3w>'Validate messaging consistency across website, GitHub repos, and local.</p> <div class="related-skill-tools" data-astro-cid-jrlgpo3w> <span class="related-tool-pill" data-astro-cid-jrlgpo3w>Read</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>WebFetch</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>WebSearch</span> </div> </a><a href="/skills/agent-context-loader/" class="related-skill-card" data-astro-cid-jrlgpo3w> <h3 class="related-skill-title" data-astro-cid-jrlgpo3w>agent-context-loader</h3> <p class="related-skill-description" data-astro-cid-jrlgpo3w>'Execute proactive auto-loading: automatically detects and loads agents.</p> <div class="related-skill-tools" data-astro-cid-jrlgpo3w> <span class="related-tool-pill" data-astro-cid-jrlgpo3w>Read</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Write</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Edit</span> </div> </a><a href="/skills/api-testing/" class="related-skill-card" data-astro-cid-jrlgpo3w> <h3 class="related-skill-title" data-astro-cid-jrlgpo3w>api-testing</h3> <p class="related-skill-description" data-astro-cid-jrlgpo3w>"Use when testing HTTP endpoints, probing URLs for status and headers, or running declarative API test suites from plain text files".</p> <div class="related-skill-tools" data-astro-cid-jrlgpo3w> <span class="related-tool-pill" data-astro-cid-jrlgpo3w>[Bash(hurl*)</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Bash(httpx*)</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Read</span> </div> </a><a href="/skills/audit-plugin/" class="related-skill-card" data-astro-cid-jrlgpo3w> <h3 class="related-skill-title" data-astro-cid-jrlgpo3w>audit-plugin</h3> <p class="related-skill-description" data-astro-cid-jrlgpo3w>Performs a deep review of the Claude Code plugin, skill, or sub-agent defined in the current project against official best practices.</p> <div class="related-skill-tools" data-astro-cid-jrlgpo3w> <span class="related-tool-pill" data-astro-cid-jrlgpo3w>Read Glob Grep Bash WebSearch WebFetch</span> </div> </a><a href="/skills/audit-standards/" class="related-skill-card" data-astro-cid-jrlgpo3w> <h3 class="related-skill-title" data-astro-cid-jrlgpo3w>audit-standards</h3> <p class="related-skill-description" data-astro-cid-jrlgpo3w>Audits the current project against the development standards defined in ~/.</p> <div class="related-skill-tools" data-astro-cid-jrlgpo3w> <span class="related-tool-pill" data-astro-cid-jrlgpo3w>Read Glob Grep Bash</span> </div> </a><a href="/skills/box-cloud-filesystem/" class="related-skill-card" data-astro-cid-jrlgpo3w> <h3 class="related-skill-title" data-astro-cid-jrlgpo3w>box-cloud-filesystem</h3> <p class="related-skill-description" data-astro-cid-jrlgpo3w>Cloud filesystem operations via Box CLI.</p> <div class="related-skill-tools" data-astro-cid-jrlgpo3w> <span class="related-tool-pill" data-astro-cid-jrlgpo3w>Read</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Write</span><span class="related-tool-pill" data-astro-cid-jrlgpo3w>Edit</span> </div> </a> </div> </section> <!-- 10. Footer --> <footer class="skill-footer" data-astro-cid-jrlgpo3w> <p data-astro-cid-jrlgpo3w>Part of <a href="/plugins/claude-workflow-skills/" data-astro-cid-jrlgpo3w>claude-workflow-skills</a></p> </footer> </div> <script> document.querySelectorAll('.install-command').forEach(el => { el.addEventListener('click', function() { const text = this.dataset.copy || this.textContent; navigator.clipboard.writeText(text).then(() => { const originalText = this.textContent; this.textContent = 'Copied to clipboard!'; this.style.color = 'var(--brand-green)'; setTimeout(() => { this.textContent = originalText; this.style.color = ''; }, 2000); }); }); }); </script> </main> <!-- Footer --> <footer data-astro-cid-37fxchfa> <div class="footer-inner" data-astro-cid-37fxchfa> <div class="footer-signup-row" data-astro-cid-37fxchfa> <h4 data-astro-cid-37fxchfa>Stay in the Loop</h4> <form data-signup-form="footer" class="footer-signup-form" data-astro-cid-37fxchfa> <input type="email" name="email" placeholder="you@example.com" required data-astro-cid-37fxchfa> <input type="text" name="website" tabindex="-1" autocomplete="off" aria-hidden="true" style="position:absolute;left:-9999px;opacity:0;height:0;width:0;" data-astro-cid-37fxchfa> <button type="submit" data-astro-cid-37fxchfa>Subscribe</button> </form> <p class="footer-form-note" data-astro-cid-37fxchfa>No spam. Unsubscribe anytime.</p> </div> <div class="footer-columns" data-astro-cid-37fxchfa> <div class="footer-col" data-astro-cid-37fxchfa> <h5 data-astro-cid-37fxchfa>Product</h5> <ul data-astro-cid-37fxchfa> <li data-astro-cid-37fxchfa><a href="/explore" data-astro-cid-37fxchfa>Explore</a></li> <li data-astro-cid-37fxchfa><a href="/skills" data-astro-cid-37fxchfa>Skills</a></li> <li data-astro-cid-37fxchfa><a href="/cowork" data-astro-cid-37fxchfa>Cowork</a></li> <li data-astro-cid-37fxchfa><a href="/compare-marketplaces" data-astro-cid-37fxchfa>Compare</a></li> <li data-astro-cid-37fxchfa><a href="/tools" data-astro-cid-37fxchfa>Tools</a></li> </ul> </div> <div class="footer-col" data-astro-cid-37fxchfa> <h5 data-astro-cid-37fxchfa>Resources</h5> <ul data-astro-cid-37fxchfa> <li data-astro-cid-37fxchfa><a href="/docs" data-astro-cid-37fxchfa>Docs</a></li> <li data-astro-cid-37fxchfa><a href="/changelog" data-astro-cid-37fxchfa>Changelog</a></li> <li data-astro-cid-37fxchfa><a href="/collections" data-astro-cid-37fxchfa>Collections</a></li> <li data-astro-cid-37fxchfa><a href="/playbooks" data-astro-cid-37fxchfa>Playbooks</a></li> <li data-astro-cid-37fxchfa><a href="/research" data-astro-cid-37fxchfa>Research</a></li> <li data-astro-cid-37fxchfa><a href="/learning" data-astro-cid-37fxchfa>Learning</a></li> </ul> </div> <div class="footer-col" data-astro-cid-37fxchfa> <h5 data-astro-cid-37fxchfa>Company</h5> <ul data-astro-cid-37fxchfa> <li data-astro-cid-37fxchfa><a href="/community" data-astro-cid-37fxchfa>Community</a></li> <li data-astro-cid-37fxchfa><a href="/community#hall-of-fame" data-astro-cid-37fxchfa>Hall of Fame</a></li> <li data-astro-cid-37fxchfa><a href="https://github.com/jeremylongshore/claude-code-plugins" target="_blank" data-astro-cid-37fxchfa>GitHub</a></li> </ul> </div> <div class="footer-col" data-astro-cid-37fxchfa> <h5 data-astro-cid-37fxchfa>Legal</h5> <ul data-astro-cid-37fxchfa> <li data-astro-cid-37fxchfa><a href="/privacy" data-astro-cid-37fxchfa>Privacy</a></li> <li data-astro-cid-37fxchfa><a href="/terms" data-astro-cid-37fxchfa>Terms</a></li> <li data-astro-cid-37fxchfa><a href="/acceptable-use" data-astro-cid-37fxchfa>Acceptable Use</a></li> </ul> </div> </div> <div class="footer-bottom" data-astro-cid-37fxchfa> <p class="footer-blurb" data-astro-cid-37fxchfa>Tons of Skills by <a href="https://intentsolutions.io" target="_blank" data-astro-cid-37fxchfa>Intent Solutions</a>. Marine. Citadel Grad. 20 years ops → self-taught dev → AI architect.</p> <p class="footer-copyright" data-astro-cid-37fxchfa>© 2026 Tons of Skills | <a href="https://intentsolutions.io" target="_blank" data-astro-cid-37fxchfa>Intent Solutions</a></p> </div> </div> </footer> <!-- Light theme global overrides (must be unscoped to target body/nav/footer) --> <!-- Email signup + killer-skill nomination forms. Posts to /api/forms/{signup,nominate} on the same origin. The endpoint is a tiny Node service on the VPS (`forms-api.service`, 127.0.0.1:8090) that forwards to the Slack webhook in /etc/intentsolutions/notify.env. Anti-spam: honeypot field `website`, email/URL regex, 8 KiB body cap, per-IP rate limit on the server. See intentsolutions-vps-runbook/docs/tonsofskills-forms-api.md. --> <script type="module"> (() => { const flash = (btn, msg, ok = true, restoreMs = 3000) => { const orig = btn.dataset.origText ?? btn.textContent; if (!btn.dataset.origText) btn.dataset.origText = orig; btn.textContent = msg; btn.style.opacity = ok ? '0.85' : '1'; btn.disabled = ok; if (!ok) setTimeout(() => { btn.textContent = orig; btn.style.opacity = ''; btn.disabled = false; }, restoreMs); }; const wireSignup = (form) => form.addEventListener('submit', async (e) => { e.preventDefault(); const btn = form.querySelector('button[type="submit"]'); const emailInput = form.querySelector('input[type="email"]'); const honey = form.querySelector('input[name="website"]'); const email = (emailInput?.value || '').trim(); if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { flash(btn, 'Invalid email', false); return; } flash(btn, 'Subscribing…', true, 0); try { const r = await fetch('/api/forms/signup', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ email, source: form.dataset.signupForm || 'unknown', website: honey?.value || '' }) }); if (r.ok) { flash(btn, "You're in!", true, 0); if (emailInput) emailInput.value = ''; } else if (r.status === 429) { flash(btn, 'Slow down', false); } else { const j = await r.json().catch(() => ({})); flash(btn, j.error || 'Something went wrong', false); } } catch { flash(btn, 'Network error', false); } }); const wireNomination = (form) => form.addEventListener('submit', async (e) => { e.preventDefault(); const btn = form.querySelector('button[type="submit"]'); const repoInput = form.querySelector('input[name="repo"]'); const honey = form.querySelector('input[name="website"]'); const repoUrl = (repoInput?.value || '').trim(); if (!/^https:\/\/github\.com\/[^/\s]+\/[^/\s]+/.test(repoUrl)) { flash(btn, 'GitHub URL only', false); return; } flash(btn, 'Submitting…', true, 0); try { const r = await fetch('/api/forms/nominate', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ repoUrl, source: form.dataset.nominationForm || 'unknown', website: honey?.value || '' }) }); if (r.ok) { flash(btn, 'Submitted!', true, 0); if (repoInput) repoInput.value = ''; } else if (r.status === 429) { flash(btn, 'Slow down', false); } else { const j = await r.json().catch(() => ({})); flash(btn, j.error || 'Something went wrong', false); } } catch { flash(btn, 'Network error', false); } }); const wireContact = (form) => form.addEventListener('submit', async (e) => { e.preventDefault(); const btn = form.querySelector('button[type="submit"]'); const nameInput = form.querySelector('input[name="name"]'); const emailInput = form.querySelector('input[type="email"]'); const messageInput = form.querySelector('textarea[name="message"]'); const honey = form.querySelector('input[name="website"]'); const name = (nameInput?.value || '').trim(); const email = (emailInput?.value || '').trim(); const message = (messageInput?.value || '').trim(); if (name.length < 2) { flash(btn, 'Name required', false); return; } if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { flash(btn, 'Invalid email', false); return; } if (message.length < 10) { flash(btn, 'Tell me more', false); return; } flash(btn, 'Sending…', true, 0); try { const r = await fetch('/api/forms/contact', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name, email, message, source: form.dataset.contactForm || 'unknown', website: honey?.value || '' }) }); if (r.ok) { flash(btn, "Got it — back within 24h!", true, 0); if (nameInput) nameInput.value = ''; if (emailInput) emailInput.value = ''; if (messageInput) messageInput.value = ''; } else if (r.status === 429) { flash(btn, 'Slow down', false); } else { const j = await r.json().catch(() => ({})); flash(btn, j.error || 'Something went wrong', false); } } catch { flash(btn, 'Network error', false); } }); const init = () => { document.querySelectorAll('[data-signup-form]').forEach(wireSignup); document.querySelectorAll('[data-nomination-form]').forEach(wireNomination); document.querySelectorAll('[data-contact-form]').forEach(wireContact); }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })(); </script> <!-- Theme Toggle Script --> <script> document.addEventListener('DOMContentLoaded', function() { var themeToggle = document.querySelector('.theme-toggle'); if (themeToggle) { themeToggle.addEventListener('click', function() { var current = document.documentElement.getAttribute('data-theme') || 'dark'; var next = current === 'dark' ? 'light' : 'dark'; document.documentElement.setAttribute('data-theme', next); localStorage.setItem('theme', next); var meta = document.querySelector('meta[name="theme-color"]'); if (meta) meta.setAttribute('content', next === 'dark' ? '#0a0a0c' : '#fafaf9'); }); } window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', function(e) { if (!localStorage.getItem('theme')) { document.documentElement.setAttribute('data-theme', e.matches ? 'light' : 'dark'); } }); }); </script> <!-- Nav Scroll State --> <script> (function() { var nav = document.querySelector('nav'); if (!nav) return; function onScroll() { if (window.scrollY > 20) { nav.classList.add('scrolled'); } else { nav.classList.remove('scrolled'); } } window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); })(); </script> <!-- Mobile Menu Script --> <script> document.addEventListener('DOMContentLoaded', function() { var menuToggle = document.querySelector('.mobile-menu-toggle'); var navLinks = document.querySelector('.nav-links'); var menuIcon = document.querySelector('.menu-icon'); var closeIcon = document.querySelector('.close-icon'); if (menuToggle && navLinks) { menuToggle.addEventListener('click', function() { navLinks.classList.toggle('active'); document.body.classList.toggle('menu-open'); if (navLinks.classList.contains('active')) { menuIcon.style.display = 'none'; closeIcon.style.display = 'block'; } else { menuIcon.style.display = 'block'; closeIcon.style.display = 'none'; } }); navLinks.querySelectorAll('a').forEach(function(link) { link.addEventListener('click', function() { navLinks.classList.remove('active'); document.body.classList.remove('menu-open'); menuIcon.style.display = 'block'; closeIcon.style.display = 'none'; }); }); document.addEventListener('click', function(e) { if (navLinks.classList.contains('active') && !navLinks.contains(e.target) && !menuToggle.contains(e.target)) { navLinks.classList.remove('active'); document.body.classList.remove('menu-open'); menuIcon.style.display = 'block'; closeIcon.style.display = 'none'; } }); } // Track outbound link clicks (nav + footer) document.querySelectorAll('nav a[target="_blank"], footer a[target="_blank"]').forEach(function(link) { link.addEventListener('click', function() { if (window.trackEvent) { window.trackEvent('outbound_click', { url: link.href, link_text: link.textContent.trim() }); } }); }); }); </script> </body> </html>