{"slug": "automating-daily-dev-chores-with-claude-code-and-github-actions-3-workflows-i", "title": "Automating Daily Dev Chores with Claude Code and GitHub Actions: 3 Workflows I Use", "summary": "A developer has automated three daily chores using Claude Code and GitHub Actions: issue triage, changelog generation, and lint fix patching. The workflows run headlessly via `claude -p` in non-interactive mode, reclaiming 6.2 hours per week at roughly $9/month in API costs. The triage workflow uses Claude Haiku to classify new issues from the repo's actual label set, reducing per-issue cost from $0.03 to $0.004.", "body_md": "After this article you'll have a GitHub Actions workflow that triages every new issue with Claude, a nightly job that rewrites your stale changelog from real commits, and a `claude`\n\nCLI step that auto-fixes failing lint on a PR branch and pushes the patch back. All three are copy-paste runnable today, and I'll show you the exact line that cost me $14 in wasted API calls before I caught it.\n\nThe blunt conclusion first: running Claude Code interactively in your terminal is great for building, but terrible for *chores*. Chores happen when you're asleep, in a meeting, or context-switched onto something else. I was spending roughly 70 minutes a day on three things — labeling issues, writing release notes, and fixing the same trivial lint failures — and none of it needed my brain.\n\nThe unlock is that `@anthropic-ai/claude-code`\n\nships a non-interactive mode. `claude -p \"prompt\"`\n\nruns headlessly, prints to stdout, and exits with a status code. That's the entire bridge between \"AI assistant\" and \"cron job\". Once it runs headlessly, GitHub Actions becomes a free scheduler with secrets management, a checkout of your repo, and write access to your PRs already wired up.\n\nMy real numbers after three weeks: 6.2 hours/week reclaimed, about $9/month in Claude API spend (I'm on a metered key for CI, separate from my Max plan), and one embarrassing incident I'll get to in the pitfalls section.\n\nThe most boring chore is the highest-value to automate, because it happens on *someone else's* schedule. Every new issue used to sit unlabeled until I got to it. Now Claude reads the issue body, picks from my actual label set, and posts a one-paragraph triage comment.\n\nThe key design choice: I pass Claude the *real* label list from the repo via `gh`\n\n, so it can't hallucinate `priority: P0`\n\nwhen my labels are `prio/high`\n\n. Grounding the model in real data is the difference between a useful bot and a noisy one.\n\n```\n# .github/workflows/triage.yml\nname: Claude issue triage\non:\n  issues:\n    types: [opened]\n\npermissions:\n  issues: write\n  contents: read\n\njobs:\n  triage:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Install Claude Code\n        run: npm install -g @anthropic-ai/claude-code\n      - name: Triage with Claude\n        env:\n          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          BODY: ${{ github.event.issue.body }}\n          NUM: ${{ github.event.issue.number }}\n        run: |\n          LABELS=$(gh label list --limit 100 --json name -q '.[].name' | paste -sd ',')\n          SUGGESTION=$(claude -p \"You are triaging a GitHub issue. \\\n            Available labels (pick 1-3 ONLY from this list): $LABELS. \\\n            Issue text: $BODY. \\\n            Reply with a JSON object: {\\\"labels\\\": [...], \\\"comment\\\": \\\"one short paragraph\\\"}\" \\\n            --model claude-haiku-4-5-20251001 --output-format json | jq -r '.result')\n          echo \"$SUGGESTION\" | jq -r '.labels[]' | while read -r L; do\n            gh issue edit \"$NUM\" --add-label \"$L\" || true\n          done\n          echo \"$SUGGESTION\" | jq -r '.comment' | gh issue comment \"$NUM\" --body-file -\n```\n\nNote I use `claude-haiku-4-5-20251001`\n\nhere, not Opus. Triage is a classification task — Haiku does it for roughly a tenth of the cost and finishes in ~4 seconds. Reserve the expensive models for jobs that actually write code. This single decision dropped my triage cost from ~$0.03 to ~$0.004 per issue.\n\nEveryone's changelog rots. Mine had a 5-week gap. Instead of a git hook nobody runs, I schedule a 2 a.m. job that diffs the last 24 hours of commits and asks Claude to summarize them into human-readable release notes — then opens a PR so I review before anything merges.\n\nThe interesting bit is grounding again: I don't ask Claude \"what changed lately?\" I *hand it the commits* and forbid invention. Here's the Python that builds the prompt and calls the SDK directly (cleaner than shelling out when you need to parse structured input):\n\n``` python\n# scripts/changelog.py\nimport subprocess, datetime, os\nfrom anthropic import Anthropic\n\ndef commits_since(hours=24):\n    since = (datetime.datetime.now(datetime.timezone.utc)\n             - datetime.timedelta(hours=hours)).isoformat()\n    out = subprocess.run(\n        [\"git\", \"log\", f\"--since={since}\", \"--no-merges\",\n         \"--pretty=format:%h %s\"],\n        capture_output=True, text=True, check=True,\n    )\n    return out.stdout.strip()\n\ndef summarize(commits: str) -> str:\n    client = Anthropic(api_key=os.environ[\"ANTHROPIC_API_KEY\"])\n    msg = client.messages.create(\n        model=\"claude-sonnet-4-6\",\n        max_tokens=800,\n        messages=[{\n            \"role\": \"user\",\n            \"content\": (\n                \"Turn these git commits into changelog bullets grouped \"\n                \"under ### Added / ### Fixed / ### Changed. \"\n                \"Use ONLY information present in the commit messages \"\n                \"— do not invent features. Commits:\\n\\n\" + commits\n            ),\n        }],\n    )\n    return msg.content[0].text\n\nif __name__ == \"__main__\":\n    commits = commits_since(24)\n    if not commits:\n        print(\"No commits in window; skipping.\")\n        raise SystemExit(0)\n    notes = summarize(commits)\n    today = datetime.date.today().isoformat()\n    with open(\"CHANGELOG.md\", \"r+\", encoding=\"utf-8\") as f:\n        old = f.read()\n        f.seek(0)\n        f.write(f\"## {today}\\n\\n{notes}\\n\\n{old}\")\n```\n\nAnd the workflow that runs it and opens a PR — `peter-evans/create-pull-request`\n\nhandles the branch and commit so you keep a human review gate:\n\n```\n# .github/workflows/changelog.yml\nname: Nightly changelog\non:\n  schedule:\n    - cron: \"0 17 * * *\"   # 02:00 JST\n  workflow_dispatch:\n\njobs:\n  changelog:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with: { fetch-depth: 0 }   # full history or --since lies to you\n      - uses: actions/setup-python@v5\n        with: { python-version: \"3.12\" }\n      - run: pip install anthropic\n      - run: python scripts/changelog.py\n        env:\n          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}\n      - uses: peter-evans/create-pull-request@v6\n        with:\n          commit-message: \"docs: nightly changelog\"\n          branch: auto/changelog\n          title: \"Nightly changelog update\"\n```\n\n`fetch-depth: 0`\n\nis not optional. The default shallow checkout grabs one commit, so `git log --since`\n\nreturns almost nothing and your changelog silently stays empty. I lost two nights to a \"working\" job that produced blank PRs because of this.\n\n`claude`\n\nauto-fix loop billed me on every push\nHere's the failure I promised. My third workflow auto-fixes lint on a PR: it runs `ruff`\n\n, and if it fails, hands the diff to Claude to fix and pushes the result. The naive version triggered `on: push`\n\n. Claude pushes a fix → that push triggers the workflow again → ruff still flags one stylistic rule Claude and the linter disagree on → Claude \"fixes\" it again → push → loop. It ran 9 times in 4 minutes before I killed it, billing an Opus call each round. $14, gone, on a missing-newline argument between two robots.\n\nTwo guards fixed it permanently. First, skip the job when the actor is the bot itself. Second, only push if the working tree actually changed:\n\n```\n# .github/workflows/autofix.yml\nname: Claude lint autofix\non:\n  pull_request:\n    types: [opened, synchronize]\n\njobs:\n  autofix:\n    if: github.actor != 'github-actions[bot]'   # break the loop\n    runs-on: ubuntu-latest\n    permissions: { contents: write, pull-requests: write }\n    steps:\n      - uses: actions/checkout@v4\n        with: { ref: ${{ github.head_ref }} }\n      - run: npm install -g @anthropic-ai/claude-code\n      - run: pip install ruff\n      - name: Fix lint with Claude\n        env:\n          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}\n        run: |\n          if ! ruff check . ; then\n            claude -p \"ruff check failed. Read the ruff output above, \\\n              edit the offending files to fix ONLY the lint errors, \\\n              and make no behavioral changes.\" \\\n              --model claude-sonnet-4-6 --permission-mode acceptEdits\n          fi\n      - name: Commit only if changed\n        run: |\n          git config user.name  \"github-actions[bot]\"\n          git config user.email \"github-actions[bot]@users.noreply.github.com\"\n          if ! git diff --quiet ; then\n            git commit -am \"style: claude lint autofix\"\n            git push\n          else\n            echo \"No changes — not pushing.\"\n          fi\n```\n\nThe `--permission-mode acceptEdits`\n\nflag is what lets Claude Code edit files unattended without an interactive approval prompt; without it the CLI hangs forever in CI waiting for a keystroke. The `git diff --quiet`\n\ncheck is your circuit breaker — if Claude decided the code was already fine, you push nothing and the cycle dies.\n\nAfter three weeks the pattern that generalizes: anything where the input is text the model can be *grounded* in (an issue body, a commit range, a linter's stderr) and the output is reviewable (a comment, a PR, a labeled item) is a great candidate. Anything that writes to production without a human gate is not — every one of my workflows ends in a PR or a comment, never a direct merge or deploy.\n\nPick the model per job: Haiku for classification, Sonnet for code edits, Opus only when you genuinely need deep reasoning. Cap your CI key with a monthly budget in the Anthropic console so a runaway loop costs you $14 of annoyance instead of a $400 invoice. Start with the issue triage workflow above — it's the lowest risk, ships value on day one, and proves out your secrets setup before you let the model touch code.", "url": "https://wpnews.pro/news/automating-daily-dev-chores-with-claude-code-and-github-actions-3-workflows-i", "canonical_source": "https://dev.to/_7fb6011b57d383122b5a/automating-daily-dev-chores-with-claude-code-and-github-actions-3-workflows-that-saved-me-6-hours-54gh", "published_at": "2026-06-06 04:19:20+00:00", "updated_at": "2026-06-06 04:42:15.992185+00:00", "lang": "en", "topics": ["ai-tools", "ai-agents", "mlops", "generative-ai", "large-language-models"], "entities": ["Claude Code", "GitHub Actions", "Anthropic"], "alternates": {"html": "https://wpnews.pro/news/automating-daily-dev-chores-with-claude-code-and-github-actions-3-workflows-i", "markdown": "https://wpnews.pro/news/automating-daily-dev-chores-with-claude-code-and-github-actions-3-workflows-i.md", "text": "https://wpnews.pro/news/automating-daily-dev-chores-with-claude-code-and-github-actions-3-workflows-i.txt", "jsonld": "https://wpnews.pro/news/automating-daily-dev-chores-with-claude-code-and-github-actions-3-workflows-i.jsonld"}}