I Was Burning $1.20 a Night on Nothing: A Claude Code Skill Library That Harvests and Curates Itself A developer who scaled from ¥100k to ¥1.2M monthly income built an autonomous skill library for Claude Code that harvests and curates itself. The system uses two shell scripts and two launchd jobs to automatically extract reusable procedures from conversation logs nightly and retire outdated skills weekly, eliminating manual maintenance. The developer emphasizes that a system that stops when you stop moving is fragile, and this automation removes the human bottleneck. Going from ¥100k/month as a student to ¥1.2M/month wasn't a matter of working harder. It was a matter of building an environment that keeps running when I don't. If you use Claude Code heavily, you eventually run into a contradiction. The more you use it, the smarter it gets. A procedure you notice mid-task and think "this pattern will come up again" gets written out to a skill file automatically. An error workaround you solved today can be pulled up instantly during a different task tomorrow. That part is genuinely convenient. But keep it up for three months and you've built a skill graveyard. A skill you created in month one quietly stops working and nobody notices. Two or three skills with similar names pile up next to each other. A procedure that last year's version of you judged "useful" now rests on assumptions that no longer hold in your current environment, so it's actively noise. Any system a human doesn't maintain will rot. And yet, tidying up skills on a regular schedule by hand doesn't stick. The more your side income grows, the more decisions you actually need to focus on, and "important but not urgent" work like file cleanup keeps getting pushed back. I hit a point where my skill directory passed 70 entries, I could no longer tell what was what, and I had to review the whole thing from scratch. That time was a pure loss. The core of this problem is that maintenance work eats human context . What matters is the judgment that produces output. Keeping the skill library — the raw material for that output — fresh is something I'd rather do without spending brainpower at all. That's what the two shell scripts and the two launchd jobs in this article do. The biggest lesson from six months of building a Claude Code autonomous environment: "work I do every day" and "work the system does every day" are completely different things. Back when I was juggling side gigs up to ¥600k/month, I was doing all of it myself. Open the checklist every morning, update content, check reports. When I lost my job for reasons outside my control and went to zero, what hit me was the fact that a system that stops when you stop moving is fragile . Rebuilding, I was strict about one thing: eliminate any structure where I'm the bottleneck. Skill harvesting and curation went from "when I feel like it" to "automatically, every night at 3:30 AM and every Sunday at 4:15 AM." That difference sounds small, but three months later it's decisive. "Skills accumulating is great, but maintenance can't keep up" is something anyone who has used Claude Code for a while will hit. Once the initial excitement phase passes, reality arrives in the form of library bloat and staleness. What this article presents is a concrete answer to that. Every line of code is quoted from what's actually running in my environment. I don't write design theory that doesn't run. In one sentence, the system is a two-stage pipeline: a nightly batch that extracts skills from conversation logs, and a weekly batch that automatically retires the expired ones . 会話ログ ~/Documents/my-knowledge-base/raw/conversations/ .md │ ▼ 毎日 3:30 AM ┌─────────────────────────────────────────────────┐ │ skill-harvest.sh │ │ │ │ .harvest-watermark で前回以降の差分だけ取得 │ │ ↓ │ │ MAX LOGS=3 本 × PER LOG BYTES=15000 B │ │ system-reminder行除去 → ダイジェスト生成 │ │ ↓ │ │ claude -p sonnet / max-budget $1.20 │ │ 「この手順、再利用できる?」と問い続ける │ │ ↓ │ │ ステージングdir に SKILL.md 生成 │ │ author: auto を保証 → ~/.claude/skills/auto/ │ └─────────────────────────────────────────────────┘ │ ▼ 蓄積 ~/.claude/skills/auto/ auto-skill ライブラリ │ ▼ 毎週日曜 4:15 AM ┌─────────────────────────────────────────────────┐ │ skill-curate.sh │ │ │ │ 実行前スナップショット .snapshots/ .tar.gz │ │ author: auto 以外は一切触れない │ │ ↓ │ │ 最終使用日を3段階で推定 │ │ 会話ログMtime → created → file mtime │ │ ↓ │ │ 30日未使用 → status: stale に書き換え │ │ 90日未使用 → .archive/ に物理退避 │ │ ↓ │ │ LLM で重複・統合候補を検出 │ │ → .curator-proposals.md に提案書生成 │ └─────────────────────────────────────────────────┘ Data flows in only one direction. Harvest is the input side, curate is the cleanup side. The two jobs run independently and never wait on each other. Three constants at the top of the script tell you the whole design philosophy. MAX LOGS=3 1回で扱うログ本数 PER LOG BYTES=15000 ログ1本あたりの取り込み上限バイト BUDGET USD=1.20 暴走防止キャップ A single conversation log can run from a few MB to tens of MB. Handing that to Claude raw sends token cost through the roof. So we build a digest. grep -v -e 'system-reminder' -e '^- a-z0-9 . :' "$f" 2 /dev/null | head -c $PER LOG BYTES system-reminder blocks are a solid mass of skill-listing noise, so they get excluded. Then we cut at the byte limit. Those two lines raise the information density at the shell level, before anything reaches Claude. The key to incremental processing is the .harvest-watermark file. if -f "$WM" ; then newlogs= "${ @f $ find "$LOGS" -name ' .md' -newer "$WM" 2 /dev/null }" else newlogs= "${ @f $ ls -t "$LOGS"/ .md 2 /dev/null }" fi The first run grabs the latest logs; every run after that only targets files newer than the watermark. Even though it fires at 3:30 AM every night, if there were no conversations that day it exits with "no new logs — skip" at zero cost. The design is built not to waste billing. The staging pattern is the crucial piece. Claude Code write-protects everything under ~/.claude/ , so you cannot have Claude write directly into ~/.claude/skills/auto/ . That's why the script uses a temporary directory as staging. STAGING=$ mktemp -d -t skill-harvest-stg cd "$STAGING" && perl -e 'alarm shift @ARGV; exec @ARGV' "$TIMEOUT SEC" \ "$CLAUDE" -p "$PROMPT" \ --model sonnet \ --permission-mode acceptEdits \ --allowedTools "Write Edit Read" \ --max-budget-usd "$BUDGET USD" "$LOG" 2 &1 < /dev/null Claude launches with $STAGING as its current directory and creates files via the relative path ./kebab-name/SKILL.md . Afterwards, the shell copies the actual files into ~/.claude/skills/auto/ . for sd in "$STAGING"/ /N ; do -f "$sd/SKILL.md" || continue name="${sd:t}" if -e "$AUTO/$name" ; then echo " $ ts exists, skip copy: $name" "$LOG" else cp -R "$sd" "$AUTO/$name" && { echo " $ ts CREATED: $name" "$LOG"; created++ ; } fi done If the name matches an existing skill, it's skip copy and nothing is copied. Duplicate creation is prevented right here. On top of that, an inline Python script guarantees that author: auto is stamped on. That field is what later lets skill-curate.sh decide "is this something I'm allowed to touch?", so it has to be applied reliably at this point. The weekly job is the more interesting one, design-wise. It always takes a snapshot first. tar czf "$SNAP/auto-$ date +%Y%m%d-%H%M%S .tar.gz" -C "$HOME/.claude/skills" \ --exclude='auto/.snapshots' --exclude='auto/.archive' auto 2 /dev/null Note how --exclude='auto/.snapshots' keeps the snapshot from recursively including itself. That one flag prevents snapshots-of-snapshots from multiplying endlessly. This script never actually deletes anything. It only mv s things into .archive/ . That's a strict non-destructive design: never perform an operation you can't undo. The safety guard sits in the first conditional check. if grep -q '^author: :space: auto' "$md"; then echo " $ ts skip not author:auto : $skill" "$LOG" continue fi Skills I made by hand, bundled skills, and ECC skills are never touched. That single author: auto line is the flag for "is this in scope for curation?" Because that guard exists, I can run the script against the entire skill directory without fear. The three-stage fallback for computing last-used date is also practical. lastlog, created, md = sys.argv + "","","" 1:4 ref = None if lastlog.strip : try: ref = float lastlog except: ref = None if ref is None and created.strip : try: ref = time.mktime datetime.datetime.strptime created.strip , "%Y-%m-%d" .timetuple except: ref = None if ref is None: ref = os.path.getmtime md First it looks for the latest mtime of a conversation log that mentions the skill name. Failing that, the created: date in the SKILL.md front matter. Failing that too, it falls back to the file's own mtime. In every case, the design avoids dying on a division by zero or an exception. Past STALE DAYS=30 it rewrites the file to status: stale ; past ARCHIVE DAYS=90 it retires it to .archive/ . Those numbers matter because they come from a rule of thumb: a skill unused for 30 days has a high chance of being genuinely forgotten, and at 90 days it's almost certainly unnecessary. Five months in with this system, these thresholds haven't produced a false positive. The LLM proposal phase runs last. if "$RUN LLM" = "nollm" && active = 2 && -x "$CLAUDE" ; then There's a condition that only starts the LLM when active = 2 skills remain. The logic is that with only one skill left, a consolidation proposal is meaningless, so don't start. The proposals are written to .curator-proposals.md by a Claude call with --max-budget-usd 5.00 . It never modifies or deletes the actual skill files — it just generates a proposal document. The final decision is mine: I read that document and decide by hand whether to merge anything. The execution times defined by the two plists. skill-harvest com.shun.skill-harvest : daily at 3:30 AM