{"componentChunkName":"component---src-templates-post-template-js","path":"/posts/claude-ticket-loop","result":{"data":{"markdownRemark":{"id":"a3459dad-2050-554b-ae53-4dd3c4e8ed7a","html":"<p><img src=\"/media/claude-ticket-loop.jpg\" alt=\"Tag-Driven Ticket Loop: A ClickUp-to-PR Bot in Claude Code\"></p>\n<p>The contract is simple: add a tag in ClickUp, and Claude Code picks the ticket up. Add ten tags, it works through ten tickets. Add none, it does nothing. The slash command below implements that — it filters a ClickUp list for a trigger tag, picks the oldest eligible ticket, plans the fix <em>before</em> touching code, branches the repos it needs, opens PRs, writes a structured completion report back to the ticket, and then <strong>re-queries the list</strong> and does it again. It stops only when the list is empty. Pair it with the <a href=\"https://rpy3.com/posts/claude-code-sandbox\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">sandbox from the previous post</a> — long-lived container, scoped <code class=\"language-text\">GH_TOKEN</code>, wide allowlist — and you get something that actually drains a backlog without blowing up the laptop it runs on.</p>\n<p>The tag is the API. The loop is in the prompt. The scheduler (if any) just wakes the whole thing up.</p>\n<h2 id=\"design-constraints\" style=\"position:relative;\"><a href=\"#design-constraints\" aria-label=\"design constraints permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Design constraints</h2>\n<ul>\n<li><strong>One ticket at a time, sequentially.</strong> Never process two tickets in parallel. A failure on ticket N must not leave ticket N+1 half-done.</li>\n<li><strong>Claim tags are load-bearing.</strong> <code class=\"language-text\">claude_in_progress</code> added <em>first</em>, before any code moves. A tick that crashes mid-flight leaves the tag in place so a human sees the abandoned claim.</li>\n<li><strong>Re-query between tickets.</strong> Don’t snapshot the list at the top and march through it. Someone may have removed the tag to cancel, or added urgent tickets. Re-filtering makes both work.</li>\n<li><strong>Hard caps.</strong> Max tickets per run, max wall-clock per run, hard-stop on any unrecoverable error. A loop without brakes is a footgun with a loop on it.</li>\n</ul>\n<h2 id=\"the-command\" style=\"position:relative;\"><a href=\"#the-command\" aria-label=\"the command permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>The command</h2>\n<p>Claude Code slash commands are just markdown files under <code class=\"language-text\">~/.claude/commands/</code> with a frontmatter <code class=\"language-text\">description</code> and a body that becomes the prompt. Here’s the loop, trimmed to the load-bearing structure:</p>\n<div class=\"gatsby-highlight\" data-language=\"markdown\"><pre class=\"language-markdown\"><code class=\"language-markdown\"><span class=\"token hr punctuation\">---</span>\n<span class=\"token title important\">description: Drain every tagged ticket from a ClickUp list — plan, branch, PR, report — one ticket at a time until the queue is empty.\n<span class=\"token punctuation\">---</span></span>\n\nYou are running a ticket-processing loop. Process eligible tickets <span class=\"token bold\"><span class=\"token punctuation\">**</span><span class=\"token content\">one at\na time, sequentially</span><span class=\"token punctuation\">**</span></span>, until no eligible tickets remain or a stop condition\nis hit. Never process two tickets in parallel.\n\n<span class=\"token title important\"><span class=\"token punctuation\">##</span> Fixed config (do not change)</span>\n\n<span class=\"token list punctuation\">-</span> <span class=\"token bold\"><span class=\"token punctuation\">**</span><span class=\"token content\">ClickUp list ID:</span><span class=\"token punctuation\">**</span></span> <span class=\"token code keyword\">`&lt;LIST_ID>`</span>\n<span class=\"token list punctuation\">-</span> <span class=\"token bold\"><span class=\"token punctuation\">**</span><span class=\"token content\">Required tags on a task:</span><span class=\"token punctuation\">**</span></span> <span class=\"token code keyword\">`claude_code`</span> AND <span class=\"token code keyword\">`&lt;project-tag>`</span>\n<span class=\"token list punctuation\">-</span> <span class=\"token bold\"><span class=\"token punctuation\">**</span><span class=\"token content\">Skip tags (already claimed):</span><span class=\"token punctuation\">**</span></span> <span class=\"token code keyword\">`claude_in_progress`</span>, <span class=\"token code keyword\">`claude_pr_opened`</span>\n<span class=\"token list punctuation\">-</span> <span class=\"token bold\"><span class=\"token punctuation\">**</span><span class=\"token content\">Repo A:</span><span class=\"token punctuation\">**</span></span> <span class=\"token code keyword\">`/workspace/repo-a`</span>\n<span class=\"token list punctuation\">-</span> <span class=\"token bold\"><span class=\"token punctuation\">**</span><span class=\"token content\">Repo B:</span><span class=\"token punctuation\">**</span></span> <span class=\"token code keyword\">`/workspace/repo-b`</span>\n<span class=\"token list punctuation\">-</span> <span class=\"token bold\"><span class=\"token punctuation\">**</span><span class=\"token content\">Default PR base branch:</span><span class=\"token punctuation\">**</span></span> <span class=\"token code keyword\">`&lt;default-base>`</span>\n<span class=\"token list punctuation\">-</span> <span class=\"token bold\"><span class=\"token punctuation\">**</span><span class=\"token content\">Max tickets per run:</span><span class=\"token punctuation\">**</span></span> 10\n<span class=\"token list punctuation\">-</span> <span class=\"token bold\"><span class=\"token punctuation\">**</span><span class=\"token content\">Hard-stop on any Error outcome</span><span class=\"token punctuation\">**</span></span> (don't try the next ticket after a failure).\n\n<span class=\"token title important\"><span class=\"token punctuation\">##</span> Outer loop</span>\n\nRepeat until one of these is true, then STOP:\n\n<span class=\"token list punctuation\">-</span> zero eligible tickets remain (print <span class=\"token code keyword\">`Queue drained`</span> and exit), OR\n<span class=\"token list punctuation\">-</span> you have processed 10 tickets in this run (print <span class=\"token code keyword\">`Per-run cap reached`</span>), OR\n<span class=\"token list punctuation\">-</span> any single ticket ended in an Error outcome (print <span class=\"token code keyword\">`Stopped after error on &lt;task-id>`</span>).\n\nEach iteration runs Steps 1–5 for exactly one ticket.\n\n<span class=\"token hr punctuation\">---</span>\n\n<span class=\"token title important\"><span class=\"token punctuation\">##</span> Step 1 — Find one eligible ticket (re-query each iteration)</span>\n\nCall <span class=\"token code keyword\">`clickup_filter_tasks`</span> on the list <span class=\"token italic\"><span class=\"token punctuation\">*</span><span class=\"token content\">fresh every iteration</span><span class=\"token punctuation\">*</span></span> — do not\ncache the list from a previous iteration. Filter tasks that have BOTH\nrequired tags and NEITHER skip tag.\n\n<span class=\"token list punctuation\">-</span> If zero eligible: exit the outer loop with <span class=\"token code keyword\">`Queue drained`</span>.\n<span class=\"token list punctuation\">-</span> Otherwise pick the <span class=\"token bold\"><span class=\"token punctuation\">**</span><span class=\"token content\">oldest</span><span class=\"token punctuation\">**</span></span> by <span class=\"token code keyword\">`date_created`</span>. Store its ID as\n  <span class=\"token code keyword\">`$TASK_ID`</span>. Do not pick more than one per iteration.\n\n<span class=\"token title important\"><span class=\"token punctuation\">##</span> Step 2 — Claim the ticket</span>\n\nOrder matters — tag first so a concurrent run (or crash) can't double-pick.\n\n<span class=\"token list punctuation\">1.</span> Add tag <span class=\"token code keyword\">`claude_in_progress`</span> to <span class=\"token code keyword\">`$TASK_ID`</span>.\n<span class=\"token list punctuation\">2.</span> Move status to <span class=\"token code keyword\">`in progress`</span> (closest analog; don't abort on status failure).\n\n<span class=\"token title important\"><span class=\"token punctuation\">##</span> Step 3 — Read the ticket fully</span>\n\nPull the full description + comments. Decide:\n\n<span class=\"token list punctuation\">-</span> <span class=\"token bold\"><span class=\"token punctuation\">**</span><span class=\"token content\">Bug or feature?</span><span class=\"token punctuation\">**</span></span> prefix = <span class=\"token code keyword\">`bugfix/`</span> or <span class=\"token code keyword\">`feature/`</span>\n<span class=\"token list punctuation\">-</span> <span class=\"token bold\"><span class=\"token punctuation\">**</span><span class=\"token content\">Base branch:</span><span class=\"token punctuation\">**</span></span> honor any explicit mention, else the default.\n<span class=\"token list punctuation\">-</span> <span class=\"token bold\"><span class=\"token punctuation\">**</span><span class=\"token content\">Branch name:</span><span class=\"token punctuation\">**</span></span> <span class=\"token code keyword\">`&lt;prefix>/&lt;task-id>-&lt;kebab-slug>`</span>, slug ≤ 40 chars.\n\n<span class=\"token title important\"><span class=\"token punctuation\">##</span> Step 3.5 — Post the plan BEFORE touching any repo</span>\n\nPost a ClickUp comment with this exact structure:\n\n<span class=\"token code keyword\">    **Plan (Claude Code)**\n    **Understanding:** &lt;1–3 sentences>\n    **Branch:** `&lt;branch>` → base `&lt;base>`\n    **Repo A changes:** &lt;bulleted list, or \"No changes needed — &lt;reason>\">\n    **Repo B changes:** &lt;bulleted list, or \"No changes needed — &lt;reason>\">\n    **Verification:** &lt;tests/builds per repo></span>\n\n<span class=\"token code keyword\">    _Remove `claude_in_progress` to cancel before this iteration finishes._</span>\n\nOnly continue after this comment is posted successfully.\n\n<span class=\"token title important\"><span class=\"token punctuation\">##</span> Step 4 — Work on both repos (sequentially)</span>\n\nFor each repo in order:\n\n1. <span class=\"token code keyword\">`cd`</span> in, <span class=\"token code keyword\">`git fetch origin`</span>, <span class=\"token code keyword\">`git checkout &lt;base>`</span>, <span class=\"token code keyword\">`git pull --ff-only`</span>.\n<span class=\"token list punctuation\">2.</span> If the plan said \"No changes needed\" with a solid reason, skip this repo\n   entirely (no branch, no PR) — record as a clean skip.\n<span class=\"token list punctuation\">3.</span> Otherwise <span class=\"token code keyword\">`git checkout -b &lt;branch-name>`</span>.\n<span class=\"token list punctuation\">4.</span> Implement the fix. Targeted edits only. Follow the repo's existing\n   conventions. Don't add \"explaining the fix\" comments in code.\n<span class=\"token list punctuation\">5.</span> Verify: repo-appropriate lint / typecheck / tests. Fix what you broke;\n   note pre-existing failures but don't fix them in this PR.\n6. <span class=\"token code keyword\">`git add -A`</span> → commit <span class=\"token code keyword\">`&lt;type>: &lt;title> (&lt;task-id>)`</span> → push.\n7. <span class=\"token code keyword\">`gh pr create --base &lt;base> --head &lt;branch>`</span> with a body that links back\n   to the ticket and lists a test plan.\n\n<span class=\"token title important\"><span class=\"token punctuation\">###</span> Failure handling inside Step 4</span>\n\nIf any step fails (can't fix build, push rejected, <span class=\"token code keyword\">`gh pr create`</span> errors):\n\n<span class=\"token list punctuation\">1.</span> Post an <span class=\"token code keyword\">`**Error (Claude Code)**`</span> comment on the ticket with the failing\n   repo, the step, and trimmed error output (~30 lines max).\n<span class=\"token list punctuation\">2.</span> Remove the <span class=\"token code keyword\">`claude_in_progress`</span> tag so a human / next run can retry.\n<span class=\"token list punctuation\">3.</span> <span class=\"token bold\"><span class=\"token punctuation\">**</span><span class=\"token content\">Hard-stop the outer loop</span><span class=\"token punctuation\">**</span></span> — do not start the next ticket. Print\n   <span class=\"token code keyword\">`Stopped after error on &lt;task-id>`</span> and exit.\n\n<span class=\"token title important\"><span class=\"token punctuation\">##</span> Step 5 — Close the ticket (structured completion report)</span>\n\nOnly if Step 4 completed for both repos (or one was a clean skip):\n\n<span class=\"token list punctuation\">1.</span> Add tag <span class=\"token code keyword\">`claude_pr_opened`</span>.\n<span class=\"token list punctuation\">2.</span> Move status to <span class=\"token code keyword\">`in review`</span>.\n<span class=\"token list punctuation\">3.</span> Post a <span class=\"token code keyword\">`**Done (Claude Code)**`</span> comment with:\n   <span class=\"token list punctuation\">-</span> PR URLs (or \"No changes needed — <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>reason</span><span class=\"token punctuation\">></span></span>\") for each repo\n   <span class=\"token list punctuation\">-</span> Branch names\n   <span class=\"token list punctuation\">-</span> 2–6 sentences on what actually changed and why\n   <span class=\"token list punctuation\">-</span> Files touched, per repo\n   <span class=\"token list punctuation\">-</span> Verification commands + pass/fail, per repo\n   <span class=\"token list punctuation\">-</span> Deviations from the Step 3.5 plan (or \"None\")\n\nReturn to the top of the outer loop (Step 1) for the next iteration.\n\n<span class=\"token title important\"><span class=\"token punctuation\">##</span> Final reminders</span>\n\n<span class=\"token list punctuation\">-</span> <span class=\"token bold\"><span class=\"token punctuation\">**</span><span class=\"token content\">Sequential, never parallel.</span><span class=\"token punctuation\">**</span></span> One ticket must fully finish before the\n  next starts.\n<span class=\"token list punctuation\">-</span> <span class=\"token bold\"><span class=\"token punctuation\">**</span><span class=\"token content\">Re-query each iteration.</span><span class=\"token punctuation\">**</span></span> Do not reuse the filtered list from the\n  previous iteration — cancellations and new tags take effect immediately.\n<span class=\"token list punctuation\">-</span> <span class=\"token bold\"><span class=\"token punctuation\">**</span><span class=\"token content\">Never force-push.</span><span class=\"token punctuation\">**</span></span> Never <span class=\"token code keyword\">`git reset --hard`</span> shared branches. Never\n  skip hooks.\n<span class=\"token list punctuation\">-</span> <span class=\"token bold\"><span class=\"token punctuation\">**</span><span class=\"token content\">Never merge the PR.</span><span class=\"token punctuation\">**</span></span> Human review is required.\n<span class=\"token list punctuation\">-</span> <span class=\"token bold\"><span class=\"token punctuation\">**</span><span class=\"token content\">Ambiguous ticket</span><span class=\"token punctuation\">**</span></span> → post a clarification-request comment, remove\n  <span class=\"token code keyword\">`claude_in_progress`</span>, SKIP to the next iteration (not a hard stop —\n  ambiguity is a per-ticket signal, not a queue-wide one).</code></pre></div>\n<h2 id=\"the-parts-that-arent-obvious\" style=\"position:relative;\"><a href=\"#the-parts-that-arent-obvious\" aria-label=\"the parts that arent obvious permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>The parts that aren’t obvious</h2>\n<p>A few design choices that look fussy on the first read but earn their keep:</p>\n<ul>\n<li><strong>Re-query between tickets, don’t cache the list.</strong> The tag is the API. If a human removes <code class=\"language-text\">claude_code</code> from ticket #3 between iterations, the agent must respect that on the next filter. Caching the initial list breaks the contract.</li>\n<li><strong>Hard-stop on error, skip on ambiguity.</strong> An <em>error</em> (build break, push rejected, MCP tool failure) means the bot’s execution model is broken — trying the next ticket will probably break the same way, and noise compounds. An <em>ambiguity</em> (conflicting comments, missing info) is a per-ticket signal — skip it and try the next one. The loop treats these differently on purpose.</li>\n<li><strong>Tag-before-status, always.</strong> If tagging succeeds but status fails, the ticket is claimed and future iterations skip it — safe. If status succeeds but tagging fails, two runs could pick the same ticket — unsafe. So we tag first and tolerate status failures.</li>\n<li><strong>Plan before code (Step 3.5).</strong> The plan comment is written <em>before</em> anything branches. This gives a human an interrupt window: remove <code class=\"language-text\">claude_in_progress</code> and the current iteration (if still mid-flight) can cancel cleanly, and future iterations won’t re-pick.</li>\n<li><strong>Per-run cap.</strong> 10 tickets is arbitrary but finite. Without it, a queue of 200 misconfigured tickets becomes 200 error comments and a surprise API bill. Pick a number you’d be okay walking into the next morning.</li>\n<li><strong>Clean skips are first-class.</strong> Lots of tickets only need work in one repo. The command explicitly supports “No changes needed — <reason>” as an outcome, recorded in both the plan and the done comment. Without this, the agent invents busywork in the second repo to avoid looking like it failed.</li>\n</ul>\n<h2 id=\"running-it\" style=\"position:relative;\"><a href=\"#running-it\" aria-label=\"running it permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Running it</h2>\n<p>The command loops internally, so no external scheduler is required — just launch once and let it drain.</p>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token comment\"># Manual: run inside the sandbox from the previous post</span>\n./sandbox/run-agent.sh\n<span class=\"token comment\"># then inside claude:</span>\n<span class=\"token operator\">></span> /clickup-tick\n\n<span class=\"token comment\"># Non-interactive (cron / CI / scheduled wake)</span>\n./sandbox/run-agent.sh claude -p <span class=\"token string\">'/clickup-tick'</span>\n\n<span class=\"token comment\"># Periodic wake-up for continuous draining. /loop just fires /clickup-tick</span>\n<span class=\"token comment\"># — the loop inside the prompt does the real work; /loop handles the gap</span>\n<span class=\"token comment\"># between \"queue empty now\" and \"new tickets tagged later\".</span>\n<span class=\"token operator\">></span> /loop 30m /clickup-tick</code></pre></div>\n<p>Two scheduling styles work:</p>\n<ul>\n<li><strong>Run-on-demand.</strong> Humans tag a batch of tickets, then kick off the command. It drains what’s there and exits. Predictable, easy to reason about cost.</li>\n<li><strong>Periodic sweep.</strong> A <code class=\"language-text\">/loop 30m /clickup-tick</code> or cron drives the bot every 30 min. Each wake-up: if the queue is empty it exits in seconds; if tickets are waiting it drains them. More hands-off, but you’re on the hook for whatever gets tagged in your sleep.</li>\n</ul>\n<p>Both rely on the same invariant: the command itself always terminates (queue drained, per-run cap, or hard-stop on error). That’s what makes it safe to wake repeatedly.</p>\n<h2 id=\"possible-risks\" style=\"position:relative;\"><a href=\"#possible-risks\" aria-label=\"possible risks permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Possible risks</h2>\n<p>Same framing as the sandbox post: blast-radius reduction, not trust-building.</p>\n<h2 id=\"when-its-working\" style=\"position:relative;\"><a href=\"#when-its-working\" aria-label=\"when its working permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>When it’s working</h2>\n<p>When it’s working, tagging a ticket and walking away results in, a few minutes later, two PR links and a completion report on the ticket — and then silence if the queue is empty, or the next ticket starting if it isn’t. When it’s not working, <code class=\"language-text\">claude_in_progress</code> gets dropped, a clarification or error comment sits on the ticket, and the outer loop stopped cleanly. The failure mode you <em>don’t</em> want — a half-done PR set with no record of what happened — is the one this design is specifically built to prevent.</p>\n<p>Quote</p>\n<blockquote>\n<p><em>Make each program do one thing well. To do a new job, build afresh rather than complicate old programs by adding new features.</em></p>\n<p>— Doug McIlroy, “Basics of the Unix Philosophy”</p>\n</blockquote>","fields":{"slug":"/posts/claude-ticket-loop","tagSlugs":["/tag/claude-code/","/tag/automation/","/tag/clickup/","/tag/slash-commands/"]},"frontmatter":{"date":"2026-04-17T23:10:00.000Z","description":"A Claude Code slash command that drains every tagged ticket from a ClickUp list — plans, implements across two repos, opens PRs, reports back — one ticket at a time, until none are left. The tag is the trigger; the loop is in the prompt.","tags":["claude-code","automation","clickup","slash-commands"],"title":"Tag-Driven Ticket Loop: A ClickUp-to-PR Bot in Claude Code","socialImage":"/media/claude-ticket-loop.jpg"}}},"pageContext":{"slug":"/posts/claude-ticket-loop"}}}