{"slug": "sending-claude-code-on-a-daily-github-patrol-auto-scoring-useful-oss-and-skills", "title": "Sending Claude Code on a Daily GitHub Patrol — Auto-Scoring Useful OSS and Skills", "summary": "A developer built github-scout.sh, a script that runs daily to patrol GitHub, auto-score useful open-source software and Claude Code skills, and inject findings into a morning brief. The script separates crawling (free) from scoring (paid Claude API) to keep costs fixed, mechanically judges skill safety by file layout, and retries failed scoring the next day. It searches across three lanes—Claude Code skills, personal OSS needs, and trending repos—and outputs only to the tooling directory without touching protected areas.", "body_md": "I kept meaning to browse GitHub for good tooling, but \"whenever I felt like it\" never turned into a habit. In the previous post, \"[Letting Claude Code Autonomously Improve Itself Unattended](https://zenn.dev/bokuwalily/articles/claude-autopilot),\" I built the skeleton of a morning brief. This time I'll write about `github-scout.sh`\n\n, which **piggybacks** on that brief-generation job: every morning it patrols GitHub, auto-scores useful OSS and skills, and injects only the items that need attention into the brief on my desktop.\n\nThere are three design points. First, separate crawling (`gh`\n\n, free) from scoring (`claude -p`\n\n, MAX plan quota) so cost stays fixed. Second, mechanically judge a skill's safety by its file layout and route it into either auto-enablement or a review-required quarantine. Third, when scoring fails, don't stamp the `seen`\n\nledger — leave it for the next morning's automatic retry.\n\nSome Claude Code skills are community-made and scattered across GitHub. The same goes for reference OSS for job-hunt trackers or Chrome extensions. But \"manually searching when the mood strikes\" isn't reproducible. I'd hit the same popular repos every time, and forget last week's discovery by the following week.\n\nI can't count how much waste came from missing a useful skill and rewriting the same thing myself. The mechanism for a morning brief already exists. Automatically injecting GitHub discoveries into it was the shortest path to a fix.\n\nIt's written right there in the script's header comment.\n\n``` php\n# CRAWL(gh=無料) -> DEDUP(shell) -> ENRICH(gh contents) -> SCORE+ROUTE+INGEST(claude -p=MAX枠) -> LEDGER\n```\n\nCost is incurred only in the scoring phase. By capping the number of candidates, I fix the token consumption of scoring.\n\n```\nMAX_CANDIDATES=30   # claude へ渡す上位件数（コスト固定化）\nMAX_ENRICH=8        # SKILL.md 中身を取りに行く skill 候補の上限\nTIMEOUT_SEC=600\n```\n\nThe output destination is only under `~/.claude/`\n\n. It never writes directly into the Vault (an iCloud-synced, TCC-protected area). This follows the same \"only touch my own tooling directory outside protected areas\" design principle as autopilot, and it's spelled out in the script's comments too.\n\n```\n# 出力:\n#   - ブリーフ差し込み用: ~/.claude/logs/github-scout-latest.md\n#   - 安全スキル(md のみ/read系権限): ~/.claude/skills/auto/<name>/ に自動有効化\n#   - Bash/script 同梱スキル: ~/.claude/skills/auto/.incoming/<name>/ に隔離 + 要対応フラグ\n#   - OSS/トレンド候補: ~/.claude/scout/watchlist.md に追記(clone しない)\n```\n\nI run `gh search repos`\n\nacross 3 purpose-specific lanes. The lanes carry straight through into the later routing.\n\n```\n# A: Claude Code スキル/プラグイン（本丸）\nrun_search skill \"A1 topic:claude-code\"   --topic claude-code --sort stars --order desc --limit 25\nrun_search skill \"A2 claude code skills\"  \"claude code skills\" --sort stars --order desc --limit 20\nrun_search skill \"A3 claude agent skill\"  \"claude agent skill SKILL.md\" --sort updated --order desc --limit 20\n\n# B: 自分の開発に直結する OSS（autofill / maps scraper / turso / resume builder / job tracker 等）\nrun_search project \"B1 chrome autofill\"     \"autofill\" --language JavaScript --stars \">100\" --sort stars --order desc --limit 12\nrun_search project \"B3 turso\"               \"turso\" --language TypeScript --stars \">50\" --sort updated --order desc --limit 12\nrun_search project \"B5 job tracker\"         \"job tracker\" --stars \">30\" --sort stars --order desc --limit 12\n# ... B2/B4/B6/B7 略\n\n# C: トレンド横断\nrun_search trend \"C1 breakout 90d\"  --created \">$D90\" --stars \">800\"  --sort stars   --order desc --limit 20\nrun_search trend \"C2 hot now\"       --updated \">$D3\"  --stars \">3000\" --sort updated --order desc --limit 20\n```\n\nEach `run_search`\n\nrecords a `❌`\n\nin the coverage table on failure and keeps going. It doesn't stop even if not all sources succeed. As a rate-limit countermeasure, I put a `sleep 1`\n\nafter each search (GitHub Search API: 30 req/min).\n\nNote:The B-series queries initially ANDed together every word across multiple topics, and zero-hit results kept piling up. As the comment`v2: 2026-06-10実測調整`\n\nrecords, I loosened them to core term + language + star floor. Compound queries can only be dialed in by trying them.\n\nThe `run_search`\n\nfunction appends lane-labeled JSONL to `raw.jsonl`\n\n.\n\n```\nrun_search() {\n  local lane=\"$1\"; shift; local label=\"$1\"; shift\n  local json\n  json=$(\"$GH\" search repos \"$@\" --json fullName,description,stargazersCount,url,language,pushedAt,createdAt 2>>\"$LOG\")\n  if [ -n \"$json\" ] && echo \"$json\" | \"$JQ\" -e 'type==\"array\"' >/dev/null 2>&1; then\n    n=$(echo \"$json\" | \"$JQ\" 'length')\n    echo \"$json\" | \"$JQ\" -c --arg lane \"$lane\" '.[] | {lane:$lane, name:.fullName, ...}' >> \"$RAW\"\n    COVERAGE=\"${COVERAGE}\\n| ${label} | ✅ | ${n} |\"\n  else\n    COVERAGE=\"${COVERAGE}\\n| ${label} | ❌ | 0 |\"\n  fi\n  sleep 1\n}\n```\n\nUsing `~/.claude/scout/seen.tsv`\n\n(repo fullName, scoring date), I filter out anything already scored, and I also apply weak duplicate detection against the existing skill names under `auto/`\n\n.\n\n```\nSEEN_SET=\"$WORK/seen.set\"\ncut -f1 \"$SEEN\" 2>/dev/null | sort -u > \"$SEEN_SET\"\n\n\"$JQ\" -s -c 'unique_by(.name) | sort_by(-.stars) | .[]' \"$RAW\" 2>/dev/null \\\n  | while IFS= read -r line; do\n      nm=$(echo \"$line\" | \"$JQ\" -r '.name')\n      grep -qxF \"$nm\" \"$SEEN_SET\" 2>/dev/null && continue\n      echo \"$line\" >> \"$FRESH\"\n    done\n```\n\n`unique_by(.name) | sort_by(-.stars)`\n\ndedupes, then orders by stars descending before taking the diff. Narrowing to the top `MAX_CANDIDATES=30`\n\nis the point where the cost ceiling is fixed.\n\nTo raise scoring quality, I fetch the root `SKILL.md`\n\ncontents and file listing only for the top `MAX_ENRICH=8`\n\nitems where `lane=skill`\n\n. Everything else is scored on metadata alone (stars/desc/language).\n\n```\nif [ \"$lane\" = \"skill\" ] && [ \"$enr_count\" -lt \"$MAX_ENRICH\" ]; then\n  enr_count=$((enr_count+1))\n  skillmd=$(\"$GH\" api \"repos/$name/contents/SKILL.md\" \\\n    --jq '.content' 2>/dev/null | base64 -d 2>/dev/null | head -c 6000)\n  files=$(\"$GH\" api \"repos/$name/contents\" \\\n    --jq '.[].name' 2>/dev/null | tr '\\n' ',' | head -c 500)\n  if echo \"$files\" | grep -qiE '\\.sh,|\\.js,|hooks,|scripts,|install|setup\\.'; then\n    has_script=\"true\"\n  fi\nfi\n```\n\nThe `has_script`\n\nflag set here is used by the later safety decision.\n\nI pass the ENRICHed candidates together into a prompt and instruct it to score ★1-5 and perform file operations at the same time. Routing goes down 3 paths.\n\n**A) Auto-adopting a skill** (lane=skill, skillmd present, ★4 or higher)\n\nThe safety rules in the prompt become the logic verbatim.\n\n```\n安全判定:\n  - allowed-tools が無い、または Read/Grep/Glob/WebFetch/WebSearch のみ → 【安全】\n  - has_script=true、または allowed-tools に Bash/Write/Edit 等が含まれる → 【要レビュー】\n- 【安全】: ~/.claude/skills/auto/<kebab名>/ を作り SKILL.md を書く。\n            先頭に provenance を必ず足す:\n            <!-- source: github-scout | repo: <name> | url: <url> | adopted: DATE -->\n            既存の auto/ に同名/酷似スキルがあればスキップ（重複禁止）。fork/mirror は取り込まない。\n- 【要レビュー】: .incoming/<kebab名>/ に SKILL.md を置く（有効化しない）。\n                  ブリーフの『要対応』に列挙。\n```\n\nNote:Skills written into`auto/`\n\nare automatically enabled at the next session startup.`.incoming/`\n\nis not enabled. If I want to enable one, the design is to promote it after review with`/scout-promote <name>`\n\n. Anything bundled with scripts (has_script=true) is always reviewed by a human.\n\n**B) OSS/project candidates** (lane=project, or lane=skill with empty skillmd, ★4 or higher)\n\nJust append to `~/.claude/scout/watchlist.md`\n\n. The format is `- [name](url) ★N — reason in 30 chars or fewer (DATE)`\n\n. No cloning.\n\n**C) Trends** (lane=trend, ★3 or higher)\n\nJust a single line in the brief. No file operations.\n\nThe section injected into the brief looks like this.\n\n```\n## 🔭 GitHub Scout (2026-06-17)\n### ⚡ 自動で入れたスキル\n- foo-skill ★4 — 何ができるか1行（有効化済み）\n### ⚠️ 要対応（あなたの確認待ち）\n- bar-skill ★5 — 何ができるか / なぜ要レビューか / 有効化するなら: /scout-promote bar-skill\n### 💡 採用候補(OSS) — watchlist追記済み\n- baz-repo ★4 — 刺さりどころ1行\n### 📈 トレンド横断\n- trending-repo ★3 — 何が新しいか1行\n```\n\nScout is not its own independent launchd job. It's called as step 2.55, immediately after brief generation (step 2.5) in `vault-auto-ingest.sh`\n\n.\n\n```\n# vault-auto-ingest.sh 抜粋 (step 2.55)\n# 2.55 GitHub Scout 巡回 → 発見/要対応をブリーフに差し込む。scout 本体は ~/.claude（保護外）のみ書く。\nrun_to 900 bash \"$HOME/.claude/scripts/github-scout.sh\" >> \"$LOG\" 2>&1 \\\n  || echo \"[$(ts)] github-scout 失敗(継続)\" >> \"$LOG\"\n\nSCOUT_SEC=\"$HOME/.claude/logs/github-scout-latest.md\"\nBRIEF_SRC=\"$VAULT/wiki/today-brief.md\"\nif [ -s \"$SCOUT_SEC\" ] && [ \"$SCOUT_SEC\" -nt \"$START_STAMP\" ] && \\\n   [ -s \"$BRIEF_SRC\" ] && [ \"$BRIEF_SRC\" -nt \"$START_STAMP\" ]; then\n  { echo \"\"; echo \"---\"; echo \"\"; cat \"$SCOUT_SEC\"; } >> \"$BRIEF_SRC\"\nfi\n```\n\nOn failure, `|| echo \"...(継続)\"`\n\nlets it move on to the next step. It doesn't halt the whole brief generation.\n\nA freshness check (`-nt \"$START_STAMP\"`\n\n) prevents an old file from being mistakenly appended. `vault-auto-ingest.sh`\n\nfires across multiple slots at 4:55 / 8:15 / 10:15 / 12:15, but after success a `DONE_MARKER`\n\nmakes subsequent slots return immediately, so once the whole brief (scout included) succeeds once, it isn't run redundantly.\n\nWhat controls what happens in the scoring phase is the `SCORE_OK`\n\nflag and `SESSION_LIMIT`\n\ndetection.\n\n```\n# Claude MAX 枠切れ（session limit）を専用検出\nSESSION_LIMIT=0\nif [ \"$rc\" -ne 0 ] && grep -qi \"session limit\" \"$RUN_OUT\" 2>/dev/null; then\n  SESSION_LIMIT=1\n  # リセットまで <=8 分なら待って1回リトライ\n  if [ \"$wait_s\" -ge 0 ] && [ \"$wait_s\" -le 480 ]; then\n    log \"session limit — ${wait_s}s 待って1回リトライ (reset=${RESET_STR:-?})\"\n    sleep \"$wait_s\"\n    run_claude; rc=$?\n  else\n    log \"session limit — reset 遠い/不明(${wait_s}s)。待たず seen 非commit で次回巡回に委ねる\"\n  fi\nfi\n```\n\nWhen the scoring result is thin (fewer than one `-`\n\nline), I set `SCORE_OK=0`\n\nand enter a 3-stage fallback.\n\n```\nCONTENT_LINES=$(grep -cE '^- ' \"$OUT\" 2>/dev/null); CONTENT_LINES=${CONTENT_LINES:-0}\n# ※ grep -c は0件でも「0」を出力し exit 1 → `|| echo 0` は 0\\n0 を生んで整数比較を壊す footgun。付けない。\nif [ \"${CONTENT_LINES:-0}\" -lt 1 ]; then\n  SCORE_OK=0\n  # 1段目: 今回クロールの TOP 候補（生データ）を jq で整形\n  FB_LIST=$(head -n 8 \"$TOP\" | \"$JQ\" -r \\\n    '\"- [\\(.name // \"?\")](\\(.url // \"\")) \\((.stars // 0))⭐ [\\(.lane // \"?\")] — \\((.desc // \"\")[0:60])\"')\n  # 2段目: TOP が空なら直近 watchlist の ★4/★5 を引く\n  # 3段目: それでも空なら明示的な失敗行（無言の空欄にしない）\nfi\n```\n\nIn the final LEDGER step, I stamp `seen.tsv`\n\nonly on scoring success.\n\n```\nif [ \"${SCORE_OK:-1}\" -eq 1 ]; then\n  while IFS= read -r line; do\n    nm=$(echo \"$line\" | \"$JQ\" -r '.name')\n    printf '%s\\t%s\\n' \"$nm\" \"$TODAY\" >> \"$SEEN\"\n  done < \"$TOP\"\nelse\n  log \"採点未成立(SCORE_OK=0) — seen 非commit。次回巡回で再採点させる。\"\nfi\n```\n\nWhen SESSION_LIMIT hits and the reset is far off, it's non-committed just like `SCORE_OK=0`\n\n. The same candidates automatically resurface in the next morning's patrol.\n\nA per-source coverage table is attached in a `<details>`\n\nat the end of the brief.\n\n```\n<details><summary>scout カバレッジ</summary>\n\n| ソース | 状態 | 件数 |\n|---|---|---|\n| A1 topic:claude-code | ✅ | 25 |\n| B1 chrome autofill | ✅ | 12 |\n| C1 breakout 90d | ❌ | 0 |\n...\n\n_raw 234 → fresh 41 → 採点 30。seen累計 187。_\n</details>\n```\n\nFollowing the \"Multi-source Coverage\" policy in CLAUDE.md, failed sources are surfaced with `❌`\n\nrather than hidden. \"採点 30\" (scored 30) is proof the `MAX_CANDIDATES`\n\ncap is in effect, letting you read off that cost is fixed.\n\n`v2: 2026-06-10実測調整`\n\n).`gh`\n\n/`jq`\n\n`--created`\n\n/`--updated`\n\ninto the query string for `gh search`\n\ngets them ignored`gh search repos`\n\nnative flags like `--created \">$D90\"`\n\n(the C-series needs this).`-s`\n\ncheck`grep -cE '^- '`\n\n(implemented in the pre-dawn of 2026-06-11 after a socket death).`grep -c`\n\nreturns exit 1 on zero matches`|| echo 0`\n\nproduces `0\\n0`\n\nand breaks the integer comparison. The script comment explicitly says \"footgun。付けない\" (footgun. don't add it).`has_script`\n\nflag from the file listing.`gh`\n\n, free) from scoring (`claude -p`\n\n, MAX plan quota), and fix cost with `MAX_CANDIDATES=30`\n\n.`seen.tsv`\n\nto prevent next-day resurfacing.`.incoming/`\n\n, and immediately enable in `auto/`\n\nonly the ones with read-only permissions.The parts I've built across this series (4-layer memory / self-propagating skills / context reduction / launchd / autopilot / long-term memory) all connect into one here. GitHub discoveries ride into the brief, and good skills become usable automatically from the next day. A loop where the environment \"reads and grows\" is now wired end to end.\n\nNext time, I wrote about how context injection ballooned as these daily-accumulating skills and briefs grew — [ The Context Audit That Cut It From 228KB to 48KB](https://zenn.dev/bokuwalily/articles/context-slimming).\n\n*Written by **Lily** — I ship iOS apps and automate my content stack with Claude Code.\n\nFollow along: [Portfolio](https://bokuwalily.com) · [X](https://x.com/bokuwalily) · [GitHub](https://github.com/bokuwalily)*", "url": "https://wpnews.pro/news/sending-claude-code-on-a-daily-github-patrol-auto-scoring-useful-oss-and-skills", "canonical_source": "https://dev.to/bokuwalily/sending-claude-code-on-a-daily-github-patrol-auto-scoring-useful-oss-and-skills-5foc", "published_at": "2026-07-13 00:00:03+00:00", "updated_at": "2026-07-13 00:14:46.453112+00:00", "lang": "en", "topics": ["developer-tools", "artificial-intelligence", "ai-agents"], "entities": ["Claude Code", "GitHub", "Claude"], "alternates": {"html": "https://wpnews.pro/news/sending-claude-code-on-a-daily-github-patrol-auto-scoring-useful-oss-and-skills", "markdown": "https://wpnews.pro/news/sending-claude-code-on-a-daily-github-patrol-auto-scoring-useful-oss-and-skills.md", "text": "https://wpnews.pro/news/sending-claude-code-on-a-daily-github-patrol-auto-scoring-useful-oss-and-skills.txt", "jsonld": "https://wpnews.pro/news/sending-claude-code-on-a-daily-github-patrol-auto-scoring-useful-oss-and-skills.jsonld"}}