<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Lily</title>
    <description>The latest articles on DEV Community by Lily (@bokuwalily).</description>
    <link>https://dev.to/bokuwalily</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4024286%2Fdf479f07-d3b3-4271-8cc4-8976224e437e.png</url>
      <title>DEV Community: Lily</title>
      <link>https://dev.to/bokuwalily</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bokuwalily"/>
    <language>en</language>
    <item>
      <title>Teaching Claude Code to Write and Grow Its Own Skills: A Self-Replicating Agent Environment</title>
      <dc:creator>Lily</dc:creator>
      <pubDate>Fri, 10 Jul 2026 17:30:33 +0000</pubDate>
      <link>https://dev.to/bokuwalily/teaching-claude-code-to-write-and-grow-its-own-skills-a-self-replicating-agent-environment-20eb</link>
      <guid>https://dev.to/bokuwalily/teaching-claude-code-to-write-and-grow-its-own-skills-a-self-replicating-agent-environment-20eb</guid>
      <description>&lt;p&gt;Last time I wrote about &lt;a href="https://zenn.dev/bokuwalily/articles/f534402187bd07" rel="noopener noreferrer"&gt;splitting Claude Code's memory into four layers&lt;/a&gt;. This is the follow-up: a setup where &lt;strong&gt;Claude Code itself discovers "reusable procedures" and accumulates them as skills&lt;/strong&gt; over time.&lt;/p&gt;

&lt;p&gt;After running this for a few months, &lt;code&gt;~/.claude/skills/auto/&lt;/code&gt; now holds &lt;strong&gt;64&lt;/strong&gt; auto-generated skills. In this article I'll cover the design of that self-replication, the mechanisms that prevent overproduction, and the generation curve I observed while operating it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: rebuilding the same procedure from scratch every time
&lt;/h2&gt;

&lt;p&gt;Claude Code is capable, but across sessions it forgets "how I got around this error last time." How to fix &lt;code&gt;launchd&lt;/code&gt;'s exit 78, the steps to bake a favicon with &lt;code&gt;png-to-ico&lt;/code&gt;, how to trim the RSC payload in Next.js — these &lt;strong&gt;procedures I should have already solved once&lt;/strong&gt; were being rebuilt from scratch every single time.&lt;/p&gt;

&lt;p&gt;I could hand-write additions to CLAUDE.md, but that turns into a "whoever notices, writes it" workflow, and that doesn't last. So I flipped the idea around and decided to &lt;strong&gt;make Claude do the work of finding the procedures too&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design: two-tier generation + Curator
&lt;/h2&gt;

&lt;p&gt;Skill generation is split into two tiers.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Timing&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Owner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;In-session generation&lt;/td&gt;
&lt;td&gt;Any time during work&lt;/td&gt;
&lt;td&gt;Free / instant&lt;/td&gt;
&lt;td&gt;Claude itself, following CLAUDE.md instructions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nightly batch generation&lt;/td&gt;
&lt;td&gt;Daily cron at 3:30&lt;/td&gt;
&lt;td&gt;Consumes Max quota&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;skill-harvest.sh&lt;/code&gt; (&lt;code&gt;claude -p&lt;/code&gt; headless)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Then a &lt;strong&gt;Curator&lt;/strong&gt; (&lt;code&gt;skill-curate.sh&lt;/code&gt;) runs weekly to periodically organize the accumulated skills. The key is separating generation and organization into different processes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 1: in-session generation (free / instant)
&lt;/h3&gt;

&lt;p&gt;CLAUDE.md says this (excerpt).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## スキル自己生成（auto-skills）&lt;/span&gt;

作業の中で再利用価値のある手順を見つけたら、頼まれなくても自分でスキル化する。
ただし乱造はしない。

&lt;span class="gu"&gt;### 生成トリガー（いずれか該当時のみ）&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; 5回以上ツールを使う非自明なタスクを最後までやり切った
&lt;span class="p"&gt;-&lt;/span&gt; エラー・行き止まりにぶつかった後、動く回避策を見つけた
&lt;span class="p"&gt;-&lt;/span&gt; ユーザーにアプローチを修正された（同じ修正を繰り返さないため）
&lt;span class="p"&gt;-&lt;/span&gt; 再利用できる手順・コマンド列・ワークフローを発見した
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The crux is "&lt;strong&gt;but don't overproduce&lt;/strong&gt;," plus narrowing the triggers down to four. Without writing this, Claude turns everything into a skill and the space fills up with noise almost immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 2: nightly batch generation (catching what was missed)
&lt;/h3&gt;

&lt;p&gt;Procedures that never got written down during a session get picked up from the conversation logs by a nightly batch. The &lt;code&gt;skill-harvest.sh&lt;/code&gt; pipeline looks like this.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read &lt;code&gt;.harvest-watermark&lt;/code&gt; (the previous processing position)&lt;/li&gt;
&lt;li&gt;Extract the diff after the watermark from the conversation logs (&lt;code&gt;raw/conversations/*.md&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Extract frequent patterns headless via &lt;code&gt;claude -p&lt;/code&gt; (&lt;strong&gt;only adopt those appearing more than 5 times&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;Deduplicate against all 64 existing skills by kebab-name&lt;/li&gt;
&lt;li&gt;Monitor token usage with a cost-guard hook; abort if the threshold is exceeded&lt;/li&gt;
&lt;li&gt;Write out new candidates to &lt;code&gt;&amp;lt;name&amp;gt;/SKILL.md&lt;/code&gt; with three sections: &lt;code&gt;frontmatter + Procedure / Pitfalls / Verification&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Trigger the Curator at the end to retire old skills&lt;/li&gt;
&lt;li&gt;Update the watermark and append to the log&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;claude -p&lt;/code&gt; is the &lt;strong&gt;headless invocation&lt;/strong&gt; I mentioned in the previous article. It runs on the Max quota, so API-key charges are zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  Isolated namespace: why split things into &lt;code&gt;auto/&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Auto-generated skills &lt;strong&gt;do not go in the same place&lt;/strong&gt; as hand-written skills or plugin-bundled skills. They're pushed into an isolated namespace, &lt;code&gt;~/.claude/skills/auto/&lt;/code&gt;. Two reasons.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;npx skills update -g&lt;/code&gt; only manages lock-registered skills. The ones in &lt;code&gt;auto/&lt;/code&gt; aren't added to the lock → &lt;strong&gt;they don't get wiped by the daily update&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The Curator only targets things that are "under &lt;code&gt;auto/&lt;/code&gt; and &lt;code&gt;author: auto&lt;/code&gt;" → &lt;strong&gt;it never touches hand-written or bundled skills&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That &lt;code&gt;author: auto&lt;/code&gt; is the Curator's target-detection key, and it's required in the frontmatter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;kebab-case&amp;gt;&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;発火条件を具体的に&amp;gt;&lt;/span&gt;
&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;auto&lt;/span&gt;          &lt;span class="c1"&gt;# ← Curatorの対象判定キー。必須&lt;/span&gt;
&lt;span class="na"&gt;created&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;YYYY-MM-DD&amp;gt;&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.0.0&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;active&lt;/span&gt;        &lt;span class="c1"&gt;# active | stale（Curatorが自動設定）&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Organizing: the Curator "never deletes — only proposes"
&lt;/h2&gt;

&lt;p&gt;Left ungoverned, skills rot. The Curator runs weekly like this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Before running, take a tar.gz snapshot into &lt;code&gt;.snapshots/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Unused for 30 days → demote to &lt;code&gt;status: stale&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Unused for 90 days → move to &lt;code&gt;.archive/&lt;/code&gt; (&lt;strong&gt;never actually deleted&lt;/strong&gt; — restorable by command)&lt;/li&gt;
&lt;li&gt;Consolidation of duplicates and low-quality skills is written to &lt;code&gt;.curator-proposals.md&lt;/code&gt; as &lt;strong&gt;proposals only&lt;/strong&gt; (not applied automatically)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;:::message&lt;br&gt;
Not letting the Curator auto-delete is the core of the safe design. "Untouched for 30 days" doesn't necessarily mean the skill is bad (you just haven't done that kind of work). So it stops at demotion and archival, leaving the final call to a human.&lt;br&gt;
:::&lt;/p&gt;
&lt;h2&gt;
  
  
  What I learned from operating it
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Generation is concentrated early, then tapers off
&lt;/h3&gt;

&lt;p&gt;Looking at the distribution of &lt;code&gt;created:&lt;/code&gt;, in the first three days after introduction (5/28–30) &lt;strong&gt;28&lt;/strong&gt; skills were verbalized in one burst, after which the pace dropped to a few per day. This matches intuition — the "unverbalized procedures" accumulated in the past get released early, and after that only newly encountered procedures get added.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;12 created: 2026-05-30
 9 created: 2026-05-29
 7 created: 2026-06-09
 6 created: 2026-06-11
 5 created: 2026-06-10
 5 created: 2026-05-28
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  "Don't overproduce" actually works
&lt;/h3&gt;

&lt;p&gt;Here's this morning's harvest log.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;会話ログを精査した結果、以下の理由から該当なし。
- Codex CLI のセットアップ → codex-cli-setup が既存
- /insights を CLAUDE.md に反映 → insights-to-claude-md が既存
- カード債務の支払い優先順位 → 個人の財務状況に依存、再利用手順に非該当
該当なし
[2026-06-15 04:11:21] harvest done (exit 0, created=0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;created=0&lt;/code&gt;. It &lt;strong&gt;rejected duplicates of existing skills, rejected one-off content tied to personal circumstances, and terminated normally with 0 generated&lt;/strong&gt;. This is proof that the "don't overproduce" design can process a miss as a &lt;em&gt;correct&lt;/em&gt; miss. If it generated recklessly here, those 64 would balloon to 200 and turn to noise fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pitfalls I hit
&lt;/h2&gt;

&lt;p&gt;From the &lt;code&gt;Pitfalls&lt;/code&gt; sections of actual skills, the ones that mattered.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;claude -p&lt;/code&gt; hits the token cap when the full system prompt loads&lt;/strong&gt; → suppress with &lt;code&gt;MAX_THINKING_TOKENS=10000&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting to advance the watermark and re-extracting the same log&lt;/strong&gt; → always check the log's last completed offset&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grepping on kebab-name alone misses fuzzy duplicates&lt;/strong&gt; → description embedding comparison is room for improvement (not implemented)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;launchd fires at 3:30, but the job is skipped while the mac is asleep&lt;/strong&gt; → recommend &lt;code&gt;pmset&lt;/code&gt; wake on schedule&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loading all MCP servers headless makes startup slow&lt;/strong&gt; → keep a harvest-only minimal config at a separate path&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Minimal setup: if you're starting today
&lt;/h2&gt;

&lt;p&gt;You don't need the full pipeline. &lt;strong&gt;Just adding one block to CLAUDE.md&lt;/strong&gt; gets Tier 1 running.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;~/.claude/skills/auto/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Write in CLAUDE.md: "When you find a reusable procedure, turn it into a skill yourself. But don't overproduce," plus the four trigger conditions&lt;/li&gt;
&lt;li&gt;Require &lt;code&gt;author: auto&lt;/code&gt; in the frontmatter (a marker for organizing later)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That alone means skills grow a little with every piece of work. You can add the nightly batch and the Curator later, once skills have accumulated and you feel like organizing them — that's soon enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Split skill generation into &lt;strong&gt;two tiers: in-session (free) and nightly batch (catching what was missed)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Spell out &lt;strong&gt;don't overproduce&lt;/strong&gt; in the prompt and narrow the triggers. A 0-generated miss is normal&lt;/li&gt;
&lt;li&gt;Isolate auto-generation in &lt;code&gt;auto/&lt;/code&gt;, and make &lt;code&gt;author: auto&lt;/code&gt; the Curator's target key&lt;/li&gt;
&lt;li&gt;The Curator &lt;strong&gt;never deletes — only demotes, archives, and proposes&lt;/strong&gt;. The final call stays with a human&lt;/li&gt;
&lt;li&gt;Start with a single block in CLAUDE.md&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next time I'll write about how these skills grew too numerous and Claude Code itself got heavy — &lt;a href="https://zenn.dev/bokuwalily/articles/context-slimming" rel="noopener noreferrer"&gt;&lt;strong&gt;the audit that cut context injection from 228KB to 48KB&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Written by **Lily&lt;/em&gt;* — I ship iOS apps and automate my content stack with Claude Code.&lt;br&gt;
Follow along: &lt;a href="https://bokuwalily.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt; · &lt;a href="https://x.com/bokuwalily" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://github.com/bokuwalily" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;*&lt;/p&gt;

</description>
      <category>claude</category>
      <category>ai</category>
      <category>automation</category>
      <category>llm</category>
    </item>
    <item>
      <title>The Day Claude Stopped Inventing My Schedule: Injecting Ground Truth into the Vault with the Google Calendar API</title>
      <dc:creator>Lily</dc:creator>
      <pubDate>Fri, 10 Jul 2026 17:28:08 +0000</pubDate>
      <link>https://dev.to/bokuwalily/the-day-claude-stopped-inventing-my-schedule-injecting-ground-truth-into-the-vault-with-the-google-39kg</link>
      <guid>https://dev.to/bokuwalily/the-day-claude-stopped-inventing-my-schedule-injecting-ground-truth-into-the-vault-with-the-google-39kg</guid>
      <description>&lt;p&gt;This is a follow-up to the Vault auto-ingest pipeline I mentioned in my previous post, "&lt;a href="https://zenn.dev/bokuwalily/articles/conversation-to-skill" rel="noopener noreferrer"&gt;Turning conversation logs into skills&lt;/a&gt;." This time I want to write about how I killed a problem where &lt;strong&gt;Claude was misreading a date range as a "block of committed time" and generating a fictional schedule out of it&lt;/strong&gt; — by injecting the actual calendar directly via the Google Calendar API.&lt;/p&gt;

&lt;p&gt;Inside my daily brief, Claude kept interpreting a note like "Breach practicum starting 6/11" as "that entire week is fully booked," and repeatedly suggesting things like "since you'll be tied up from 6/11 onward, you should move X up." The cause was that Claude was reading a date written in the prose of a note as "occupied by an appointment for that whole period." (You can still see &lt;code&gt;(2026-06-11 Breach実習)対策&lt;/code&gt; — "countermeasure for the 2026-06-11 Breach practicum" — in a comment in &lt;code&gt;vault-auto-ingest.sh&lt;/code&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: a date range in prose ≠ a commitment on the calendar
&lt;/h2&gt;

&lt;p&gt;The notes accumulated in my Vault (Obsidian) describe periods in prose, like "XX practicum M/D–M/D." Claude reads these to build the daily brief, but &lt;strong&gt;it frequently misread "the two dates, a start point and an end point" as "committed for the entire span in between."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The fundamental problem was that the only information Claude could reference about "what happens when" was the prose in the notes. The actual data from the calendar app (title, start time, end time) did not exist in the Vault at all.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The inference "there's a date in the note = fully committed for that whole period" is a result of the LLM trying to fill in context from natural language. &lt;strong&gt;The more ambiguous the information, the harder the LLM tries to fill in the gaps, and the further that filled-in content drifts from reality.&lt;/strong&gt; The only fix is to explicitly give it the correct schedule information.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The solution: generate a "canonical schedule" via the Google Calendar API
&lt;/h2&gt;

&lt;p&gt;The answer is simple. Every morning, fetch the next 21 days of events from the Google Calendar API, convert them to Markdown, and explicitly inject that into the daily brief prompt with the instruction "read this file as the single canonical schedule, and prioritize it over the prose in the notes."&lt;/p&gt;

&lt;p&gt;The pipeline has three stages.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;gcal-snapshot.py&lt;/code&gt; — fetch events via the Google Calendar API and convert to Markdown&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;vault-auto-ingest.sh&lt;/code&gt; — save to &lt;code&gt;_calendar-snapshot.md&lt;/code&gt; with last-known-good preservation logic&lt;/li&gt;
&lt;li&gt;daily brief generation via &lt;code&gt;claude -p&lt;/code&gt; — inject &lt;code&gt;CAL_SNAPSHOT_NOTE&lt;/code&gt; into the prompt&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  gcal-snapshot.py: ADC auth, automatic enumeration of all calendars, dedup
&lt;/h2&gt;

&lt;p&gt;For authentication I use &lt;strong&gt;gcloud ADC (Application Default Credentials)&lt;/strong&gt;. Instead of carrying around a &lt;code&gt;credentials.json&lt;/code&gt; and token file for an OAuth Desktop App, you just run &lt;code&gt;gcloud auth application-default login --scopes=https://www.googleapis.com/auth/calendar.readonly&lt;/code&gt; once, and &lt;code&gt;google.auth.default()&lt;/code&gt; works as-is. It doesn't hit TCC (macOS privacy protection) either, and being able to run it unattended from launchd was the deciding factor.&lt;/p&gt;

&lt;p&gt;The overall skeleton of the script looks like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
Google カレンダー実体スナップショット生成（headless / launchd 可）。
- 認証: gcloud ADC（calendar.readonly スコープ）。MCP/TCC 不要 → 無人実行可。
- アカウント配下の全カレンダーを自動列挙（新規追加も自動で拾う）。
- ノイズ除外: SNS投稿管理カレンダー・日本の祝日。
- 重複dedup: (タイトル+開始時刻) が同じイベントは1件に畳む。
- 出力: stdout に Markdown 本文（イベント1件以上で正常終了0、0件/失敗は非ゼロ）。
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;

&lt;span class="n"&gt;JST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;HORIZON_DAYS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;

&lt;span class="c1"&gt;# カレンダーID（部分一致）でのブロックリスト
&lt;/span&gt;&lt;span class="n"&gt;BLOCK_CAL_SUBSTR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9ea30ef811a373ec8fe120f0633630c8685127f65fee2cb122c7b625ffdbd954&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# SNS投稿管理(日次)
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;24d111d02338025b6a82ef07f6971ce30b3e0624c6620ee73d9f88c123adc47f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# SNS投稿管理(管理用)
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;holiday@group.v.calendar.google.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                &lt;span class="c1"&gt;# 日本の祝日
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_service&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;google.auth&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;googleapiclient.discovery&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;disc&lt;/span&gt;
    &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;scopes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.googleapis.com/auth/calendar.readonly&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;disc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;calendar&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cache_discovery&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It automatically enumerates all calendars with &lt;code&gt;calendarList().list()&lt;/code&gt;, then skips IDs that partially match the block list. Even if you add a new calendar, it's automatically included — management cost is zero.&lt;/p&gt;

&lt;p&gt;Event fetching and dedup work like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;timed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cid&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;BLOCK_CAL_SUBSTR&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;svc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;events&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;calendarId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeMin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tmin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeMax&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tmax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;singleEvents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;startTime&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxResults&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;  &lt;span class="c1"&gt;# 個別カレンダー失敗は無視（他で補う）
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cancelled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(無題)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;en&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dateTime&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# 時間ブロック予定
&lt;/span&gt;            &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dateTime&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;          &lt;span class="c1"&gt;# ← dedup: (タイトル+開始時刻) で重複排除
&lt;/span&gt;            &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;# ...行フォーマット
&lt;/span&gt;        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="c1"&gt;# 終日（締切・期間）
&lt;/span&gt;            &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;en&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 終日endは翌日0時=排他
&lt;/span&gt;                &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why dedup is necessary&lt;/strong&gt;: when the same event is registered in multiple calendars (personal, university, shared), the API returns the same event multiple times. A &lt;code&gt;seen&lt;/code&gt; set keyed on &lt;code&gt;(title, start_time)&lt;/code&gt; collapses identical events into one.&lt;/p&gt;

&lt;p&gt;The output is Markdown in the format &lt;code&gt;• 7/15(火) 10:00-12:00  XX practicum @XX University&lt;/code&gt;. &lt;strong&gt;By separating timed blocks from all-day items (deadlines and periods) into different sections&lt;/strong&gt;, I prevent Claude from confusing "today's committed hours" with "a deadline date."&lt;/p&gt;

&lt;p&gt;The script is designed so that "zero events or failure → non-zero exit," letting the caller determine success or failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  vault-auto-ingest.sh: last-known-good preservation logic
&lt;/h2&gt;

&lt;p&gt;This is the bash on the caller side that invokes &lt;code&gt;gcal-snapshot.py&lt;/code&gt; (step 2.45 of &lt;code&gt;vault-auto-ingest.sh&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 2.45 カレンダー実体スナップショット（ground truth）&lt;/span&gt;
&lt;span class="nv"&gt;CAL_SNAPSHOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VAULT&lt;/span&gt;&lt;span class="s2"&gt;/wiki/_calendar-snapshot.md"&lt;/span&gt;
&lt;span class="nv"&gt;CAL_LASTGOOD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CAL_SNAPSHOT&lt;/span&gt;&lt;span class="s2"&gt;.lastgood"&lt;/span&gt;
&lt;span class="nv"&gt;CAL_TMP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;CAL_SRC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;

&lt;span class="c"&gt;# (1) 第一候補: Google Calendar API&lt;/span&gt;
run_to 90 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PY3&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"~/.claude/scripts/gcal-snapshot.py"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CAL_TMP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; /usr/bin/grep &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"•"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CAL_TMP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;CAL_SRC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gcal-api"&lt;/span&gt;

&lt;span class="c"&gt;# (2) フォールバック: icalBuddy（ローカル Apple Calendar）&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CAL_SRC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;ICALBUDDY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;c &lt;span class="k"&gt;in&lt;/span&gt; /opt/homebrew/bin/icalBuddy /usr/local/bin/icalBuddy&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;ICALBUDDY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;break
  &lt;/span&gt;&lt;span class="k"&gt;done
  if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ICALBUDDY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"# カレンダー実体スナップショット（今後21日・自動生成）"&lt;/span&gt;
      &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"_generated &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="s1"&gt;'+%F %T'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; by vault-auto-ingest.sh / icalBuddy(fallback)_"&lt;/span&gt;
      run_to 60 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ICALBUDDY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-nc&lt;/span&gt; &lt;span class="nt"&gt;-iep&lt;/span&gt; &lt;span class="s2"&gt;"title,datetime,location"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-po&lt;/span&gt; &lt;span class="s2"&gt;"datetime,title"&lt;/span&gt; eventsToday+21
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CAL_TMP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null
    /usr/bin/grep &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"•"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CAL_TMP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;CAL_SRC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"icalbuddy"&lt;/span&gt;
  &lt;span class="k"&gt;fi
fi

if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CAL_SRC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CAL_TMP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CAL_SNAPSHOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CAL_SNAPSHOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CAL_LASTGOOD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;           &lt;span class="c"&gt;# ← 成功したら last-good を更新&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CAL_LASTGOOD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
  &lt;span class="c"&gt;# 取得失敗: 前回goodを温存し stale 印で上書き&lt;/span&gt;
  &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"# カレンダー実体スナップショット（今後21日・自動生成）"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"_generated &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="s1"&gt;'+%F %T'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; by vault-auto-ingest.sh_"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"⚠️ 本日カレンダー取得失敗。以下は前回取得成功時点の値(stale)。"&lt;/span&gt;
    &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; +4 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CAL_LASTGOOD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CAL_SNAPSHOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"# カレンダー実体スナップショット（今後21日・自動生成）"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"(取得失敗・前回値なし)"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CAL_SNAPSHOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Design intent of the 3-tier fallback:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Priority&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;①&lt;/td&gt;
&lt;td&gt;Google Calendar API (gcal-snapshot.py / ADC)&lt;/td&gt;
&lt;td&gt;Always attempted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;②&lt;/td&gt;
&lt;td&gt;icalBuddy (local Apple Calendar)&lt;/td&gt;
&lt;td&gt;On API failure. Permission exists only in interactive runs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;③&lt;/td&gt;
&lt;td&gt;last-known-good (previous success preserved with a stale marker)&lt;/td&gt;
&lt;td&gt;When both fail&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The important point is that &lt;strong&gt;a failure never wipes the snapshot to empty&lt;/strong&gt;. Even if ADC auth has expired and the API dies, it keeps the last correct snapshot with a stale marker attached. Claude is instructed by prompt to treat anything marked &lt;code&gt;⚠️ stale&lt;/code&gt; as "this is old information."&lt;/p&gt;

&lt;h2&gt;
  
  
  Injection into the daily brief prompt
&lt;/h2&gt;

&lt;p&gt;After fetching the calendar snapshot, I assemble the following string as &lt;code&gt;CAL_SNAPSHOT_NOTE&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;CAL_SNAPSHOT_NOTE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"予定・締切は &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CAL_SNAPSHOT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;（カレンダー実体・時刻付き）を唯一の正典スケジュールとして読み、ノートの散文より優先しろ。冒頭に⚠️staleとあれば取得日時点の値である点を断れ。"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gets embedded directly into the &lt;code&gt;claude -p&lt;/code&gt; prompt for daily brief generation (excerpt).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VAULT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; run_to 900 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CLAUDE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s2"&gt;"...【厳守】ノートに無い情報を推測で足すな。特に予定の拘束時間・所要日数・
『日中が消える』等の時間コストは、実際の予定記述が無い限り書くな。
ノート中の『M/D〜M/D』は開始と終了の点であって全期間拘束ではない——
拘束日数や所要時間を捏造するな。日付・時間に関する主張は確定情報と推測を
明示的に分けろ。&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CAL_SNAPSHOT_NOTE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
出力は &lt;/span&gt;&lt;span class="nv"&gt;$VAULT&lt;/span&gt;&lt;span class="s2"&gt;/wiki/today-brief.md に毎回上書き..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--dangerously-skip-permissions&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The phrase "prioritize it over the prose in the notes" is what does the work. Before I wrote that, Claude would try to "fill in" the schedule by comprehensively referencing articles across the Vault. By pinning the canon to a single file and explicitly making it take precedence, I was able to suppress the gap-filling.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Explicitly writing "the single canonical source" into the prompt&lt;/strong&gt; is the core of the effect. With "use this as a reference," Claude tries to integrate it with other information sources. By asserting "believe only this," the mistaken inferences from prose stop.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  launchd: a 4-slot self-healing design
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;com.shun.vault-auto-ingest.plist&lt;/code&gt; fires at four time slots.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;StartCalendarInterval&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&amp;lt;key&amp;gt;&lt;/span&gt;Hour&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&amp;lt;integer&amp;gt;&lt;/span&gt;4&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&amp;lt;key&amp;gt;&lt;/span&gt;Minute&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&amp;lt;integer&amp;gt;&lt;/span&gt;55&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&amp;lt;key&amp;gt;&lt;/span&gt;Hour&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&amp;lt;integer&amp;gt;&lt;/span&gt;8&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&amp;lt;key&amp;gt;&lt;/span&gt;Minute&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&amp;lt;integer&amp;gt;&lt;/span&gt;20&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&amp;lt;key&amp;gt;&lt;/span&gt;Hour&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&amp;lt;integer&amp;gt;&lt;/span&gt;10&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&amp;lt;key&amp;gt;&lt;/span&gt;Minute&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&amp;lt;integer&amp;gt;&lt;/span&gt;45&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&amp;lt;key&amp;gt;&lt;/span&gt;Hour&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&amp;lt;integer&amp;gt;&lt;/span&gt;12&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&amp;lt;key&amp;gt;&lt;/span&gt;Minute&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&amp;lt;integer&amp;gt;&lt;/span&gt;15&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it launches at 4:55 while the machine is asleep, it re-fires at 8:20 when the Mac wakes up. Because the bash side has a &lt;code&gt;DONE_MARKER&lt;/code&gt; and is written to "exit immediately (exit 0) if today's run already succeeded," even if all four slots fire, it actually runs only once.&lt;/p&gt;

&lt;p&gt;"Auto-retry until success, then no-op after success" is the core philosophy: whether it's a temporary ADC auth failure, a freeze from sleep, or no network connection, it self-heals at the next slot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pitfalls I hit
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;I first tried an OAuth Desktop App, but it wouldn't run from launchd&lt;/strong&gt; → the paths to &lt;code&gt;credentials.json&lt;/code&gt; + &lt;code&gt;token.json&lt;/code&gt; don't resolve in launchd's minimal environment. Switching to gcloud ADC (&lt;code&gt;gcloud auth application-default login&lt;/code&gt;) solved it immediately. Note that ADC's &lt;code&gt;calendar.readonly&lt;/code&gt; scope must be specified explicitly with the &lt;code&gt;--scopes&lt;/code&gt; flag.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The &lt;code&gt;icalBuddy&lt;/code&gt; fallback silently returns zero events from launchd without permission&lt;/strong&gt; → an interactive run has TCC permission for Apple Calendar, but launchd is not granted it. A check that verifies the content with &lt;code&gt;grep -q "•"&lt;/code&gt; before adopting it is essential. Overwriting the snapshot with an empty file makes the calendar look like "no appointments."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;An all-day event's &lt;code&gt;end.date&lt;/code&gt; is exclusive (midnight of the next day)&lt;/strong&gt; → this is a Google Calendar API spec. A one-day event is &lt;code&gt;start.date: 2026-07-15&lt;/code&gt;, &lt;code&gt;end.date: 2026-07-16&lt;/code&gt;. For the display label, apply &lt;code&gt;end - timedelta(days=1)&lt;/code&gt; to show the final day. If you don't know this, it gets mis-displayed as "the two days 7/15–7/16."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The same event registered in multiple calendars gets duplicated&lt;/strong&gt; → there were cases where the same class was in both the university's timetable calendar and my personal calendar. Until I deduped with the &lt;code&gt;seen&lt;/code&gt; set keyed on &lt;code&gt;(title, start_time)&lt;/code&gt;, the same class was output as 2–3 lines.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ADC tokens may need refreshing every 90 days&lt;/strong&gt; → &lt;code&gt;gcloud auth application-default login&lt;/code&gt; requires re-authentication after a long period. If the script emits &lt;code&gt;AUTH_ERROR&lt;/code&gt; to stderr, that's the sign to re-authenticate. Thanks to last-good preservation the service doesn't stop, but if &lt;code&gt;⚠️ stale&lt;/code&gt; keeps appearing in &lt;code&gt;_calendar-snapshot.md&lt;/code&gt;, be suspicious.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Claude misreads a date range as a "block of committed time" &lt;strong&gt;because it lacks accurate schedule information and tries to fill in the gaps from the prose in the notes&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gcal-snapshot.py&lt;/code&gt; is headless and launchd-ready via &lt;strong&gt;gcloud ADC + &lt;code&gt;google.auth.default()&lt;/code&gt;&lt;/strong&gt;, with automatic enumeration of all calendars, a block list, and dedup included&lt;/li&gt;
&lt;li&gt;On fetch failure, it &lt;strong&gt;preserves the last-known-good with a stale marker&lt;/strong&gt; and never wipes the snapshot to empty&lt;/li&gt;
&lt;li&gt;The core of the effect is explicitly injecting "&lt;strong&gt;the single canonical schedule, prioritized over the prose in the notes&lt;/strong&gt;" into the daily brief prompt&lt;/li&gt;
&lt;li&gt;launchd is a &lt;strong&gt;4-slot self-healing design&lt;/strong&gt; (a done marker prevents double execution, and the next slot retries on failure)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next time I'll write about a stop hook runaway that spun off from this pipeline and made me want to shut it down — &lt;strong&gt;how I pinpointed the cause of a cost spike using the transcript and fixed it&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Written by **Lily&lt;/em&gt;* — I ship iOS apps and automate my content stack with Claude Code.&lt;br&gt;
Follow along: &lt;a href="https://bokuwalily.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt; · &lt;a href="https://x.com/bokuwalily" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://github.com/bokuwalily" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;*&lt;/p&gt;

</description>
      <category>claude</category>
      <category>automation</category>
      <category>googlecalendar</category>
      <category>python</category>
    </item>
    <item>
      <title>Splitting Work Between Claude Code and Codex on One Mac: Claude Designs, Codex Implements</title>
      <dc:creator>Lily</dc:creator>
      <pubDate>Fri, 10 Jul 2026 16:21:33 +0000</pubDate>
      <link>https://dev.to/bokuwalily/splitting-work-between-claude-code-and-codex-on-one-mac-claude-designs-codex-implements-1di2</link>
      <guid>https://dev.to/bokuwalily/splitting-work-between-claude-code-and-codex-on-one-mac-claude-designs-codex-implements-1di2</guid>
      <description>&lt;p&gt;This is a continuation of my "Claude Code environment" series, following &lt;a href="https://zenn.dev/bokuwalily/articles/f534402187bd07" rel="noopener noreferrer"&gt;How I split Claude Code's memory into four layers&lt;/a&gt;. This time I want to write about &lt;strong&gt;making Claude Code and Codex CLI divide the work between them inside a single Mac&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The trigger was simple: when I run my main session on a lightweight model, &lt;strong&gt;it feels wasteful to burn tokens on the long typing of implementation&lt;/strong&gt;. Design and review benefit from how smart the model is, but the process of just churning out predetermined implementation is something I'd rather offload to a separate process. So I settled on delegating implementation to Codex while keeping design, research, and review on the Claude side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dividing the work: separate the brain from the hands
&lt;/h2&gt;

&lt;p&gt;I've spelled out the division of labor in CLAUDE.md. The gist is this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Process&lt;/th&gt;
&lt;th&gt;Owner&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Design, codebase research, planning&lt;/td&gt;
&lt;td&gt;Claude (main)&lt;/td&gt;
&lt;td&gt;Requires understanding context and judging trade-offs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Implementation, refactoring, test generation&lt;/td&gt;
&lt;td&gt;Codex&lt;/td&gt;
&lt;td&gt;Offload the boilerplate, large-scale, and repetitive work to the typing side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code review&lt;/td&gt;
&lt;td&gt;Both&lt;/td&gt;
&lt;td&gt;Add a second engine's eyes to raise quality&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key point is &lt;strong&gt;not to delegate everything&lt;/strong&gt;. When an implementation is highly difficult, or involves heavy design judgment, the main session that holds the context can write it directly, faster and more accurately. Delegation is strictly limited to the "mass-produce the predetermined stuff" process.&lt;/p&gt;

&lt;p&gt;:::message&lt;br&gt;
The axis is separating the processes that need the model's intelligence (design, review) from those that need volume more than intelligence (boilerplate implementation). It's not "since I have two AIs, parallelism makes it faster" — it's "choose which work to seat in the expensive chair."&lt;br&gt;
:::&lt;/p&gt;
&lt;h2&gt;
  
  
  How to invoke it: fire it off headlessly and safely
&lt;/h2&gt;

&lt;p&gt;Codex is installed as a CLI (&lt;code&gt;codex-cli 0.135.0&lt;/code&gt;). I delegate via &lt;strong&gt;non-interactive headless execution&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Implementation / task delegation (you can reference files with @file)&lt;/span&gt;
&lt;span class="nb"&gt;timeout &lt;/span&gt;600 codex &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--skip-git-repo-check&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;request&amp;gt;"&lt;/span&gt; &amp;lt;/dev/null

&lt;span class="c"&gt;# Code review (run against the current repo)&lt;/span&gt;
codex &lt;span class="nb"&gt;exec &lt;/span&gt;review &amp;lt;/dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are three small but effective points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Close stdin with &lt;code&gt;&amp;lt;/dev/null&lt;/code&gt;&lt;/strong&gt;. If you don't, Codex waits for interactive input and freezes, staying stuck all the way to the &lt;code&gt;timeout&lt;/code&gt; for nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrap it in &lt;code&gt;timeout&lt;/code&gt;&lt;/strong&gt;. Headless AI processes occasionally never finish. Setting things up so the OS can reap them keeps stuck processes from piling up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--skip-git-repo-check&lt;/code&gt;&lt;/strong&gt;. Needed when running in a directory outside a git repo. Without it, the repo check rejects the run.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The PATH trap
&lt;/h3&gt;

&lt;p&gt;Codex is installed under nvm. Claude's Bash during an interactive session has already loaded the nvm default, so &lt;code&gt;codex&lt;/code&gt; just works — but &lt;strong&gt;from a bare zsh or via cron, you can get &lt;code&gt;command not found&lt;/code&gt;&lt;/strong&gt;. In that case, call it by its full path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/Users/&amp;lt;you&amp;gt;/.nvm/versions/node/v24.13.0/bin/codex &lt;span class="nb"&gt;exec&lt;/span&gt; ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you run it unattended from launchd or cron, nvm isn't in the minimal PATH, so you're guaranteed to trip on this. Specifying the full path is the reliable fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  The delegation flow
&lt;/h2&gt;

&lt;p&gt;The actual operational flow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Main (Claude)&lt;/strong&gt;: receives the requirements, researches the codebase, and lays out the design and plan&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Judge the scale&lt;/strong&gt;: if it's boilerplate, large-scale, or repetitive, delegate; if it's a hard spot, implement it directly in main&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delegate (Codex)&lt;/strong&gt;: throw the concrete task to &lt;code&gt;codex exec&lt;/code&gt; and have it implement&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review (Codex)&lt;/strong&gt;: once implementation is done, run &lt;code&gt;codex exec review&lt;/code&gt; for code review&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When main gets stuck&lt;/strong&gt;: hand it off to Codex&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Splitting steps 3 and 4 across different engines is quietly effective. When the same model reviews as "the person who wrote it," the review tends to go soft, but by having Codex write and Codex review — or having Claude review — you can &lt;strong&gt;separate the author from the reviewer&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't break the handoff: passing state through files
&lt;/h2&gt;

&lt;p&gt;For long delegations, I pass tasks, handoffs, and state &lt;strong&gt;through files&lt;/strong&gt;. Conversation context is volatile, so writing the state to disk makes resuming and verifying easier. When I run things isolated in a worktree, I write the branch and worktree path into a status file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Status&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; State: running
&lt;span class="p"&gt;-&lt;/span&gt; Updated: 2026-06-17T...
&lt;span class="p"&gt;-&lt;/span&gt; Branch: feat/...
&lt;span class="p"&gt;-&lt;/span&gt; Worktree: &lt;span class="sb"&gt;`/path/to/worktree`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the same philosophy as &lt;a href="https://zenn.dev/bokuwalily/articles/f534402187bd07" rel="noopener noreferrer"&gt;the four-layer memory piece&lt;/a&gt;. &lt;strong&gt;Keep the canonical copy of state outside the volatile conversation.&lt;/strong&gt; Whether it's collaboration or long-term memory, the principle that works turned out to be the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pitfalls I hit
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting &lt;code&gt;&amp;lt;/dev/null&lt;/code&gt; and Codex freezing while waiting for input&lt;/strong&gt; → always close stdin&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;codex: command not found&lt;/code&gt; from cron/launchd&lt;/strong&gt; → it's under nvm, so the full path is mandatory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Running outside a git repo gets rejected&lt;/strong&gt; → &lt;code&gt;--skip-git-repo-check&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delegating everything and losing context&lt;/strong&gt; → write the hard spots directly in main; limit delegation to boilerplate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Soft reviews when author = reviewer&lt;/strong&gt; → split implementation and review across different engines&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Within one machine, split &lt;strong&gt;design and review to Claude, boilerplate implementation to Codex&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Delegate via non-interactive execution: &lt;code&gt;codex exec --skip-git-repo-check "..." &amp;lt;/dev/null&lt;/code&gt; wrapped in &lt;code&gt;timeout&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't delegate everything.&lt;/strong&gt; Write the hard spots directly in main&lt;/li&gt;
&lt;li&gt;Split implementation and review across different engines to raise quality&lt;/li&gt;
&lt;li&gt;Write state to files. Keep the canonical copy outside the volatile conversation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next time I'll write about &lt;strong&gt;a hook that mechanically stops secrets from leaking&lt;/strong&gt; right before that push — &lt;a href="https://zenn.dev/bokuwalily/articles/pre-push-secret-guard" rel="noopener noreferrer"&gt;Stopping API keys and accidental pushes with a pre-push guard&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Written by **Lily&lt;/em&gt;* — I ship iOS apps and automate my content stack with Claude Code.&lt;br&gt;
Follow along: &lt;a href="https://bokuwalily.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt; · &lt;a href="https://x.com/bokuwalily" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://github.com/bokuwalily" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;*&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>codex</category>
      <category>ai</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
