ブログ
RSS技術やプロダクト開発に関する知見を発信しています
規約は、規約について嘘をつく
エージェントに読ませる規約が、実際の挙動と食い違ったまま残っていました。参照ファイルを読めと明示的に書いた指示が、その通りには動いていなかった件を起点に、規約そのものを検査した記録です。
Required Status Check を CI Result 1つに集約している
GitHub の Required Status Check はワークフローを区別せず、Check Run の名前だけで照合します。ラビーでは CI を `ci.yaml` 1本にまとめ、全ジョブの結果を集める `CI Result` ジョブだけを Required にしています。`always()` を外すと落ちたジョブが成功扱いになる根拠、Fork からの PR に Required Check が効かない理由と `pull_request_target` を使わない判断を書きます。
自律ループを作って、測って、消した
「ループエンジニアリング」という語ができる4か月前に、自律的な精製ループを実装していました。半年近く後に利用ゼロを実測して削除し、残したのは上限つきリトライだけです。提唱側と批判側の議論に照らして、その判断がどこに位置するかを整理します。
Renovate の Auto-merge 対象から Digest を外した
GitHub Actions の commit SHA Pin は、その SHA を書き換える Renovate の PR を人が見なければ守りになりません。SHA Pin とフル semver のコメントが揃っていれば Digest 更新が単独で来るのはリリース済みのタグが動いたときだけなので、ラビーの共有 Preset の `automerge` 対象から Digest を外しました。`minimumReleaseAge` では止められない理由、Pin のコメントの揃え方、先に試した例外ルール、タグ Ruleset と `sha_pinning_required` も記録します。
落ちないテストは、何も証明しない
検証の規約を明文化して3週間後、レビュー担当がそれを実行していなかったことが分かりました。答えは規約をもう1つ足すことではなく、検査を人の手から外して、通らなければマージそのものを止めることでした。
EKEventStoreChanged は自分自身の変更でも飛んでくる
EventKit の EKEventStoreChanged は外部プロセスだけでなく自分の save() でも飛ぶため、Vigilare では完了チェック1回でリマインダー全件フェッチが2回走っていました。通知には誰が変更したのかを示す情報がないので、操作中に届いた通知は捨てて操作の最後に必ず読み直す形にし、実機で全件フェッチを1回に減らしました。
「保存した」は検証ではない
エージェントに作らせた画面の見た目を、数値ゲート・オフスクリーンのPNG・実際の導線の三段で確認しています。撮影までは自動化できても、撮った絵を見て判断する行為だけは自動化できていません。
Claude Code の Auto mode では Edit ツールが使われなくなる
Claude CodeのAuto modeでは、ファイル編集をsedやヒアドキュメントで済ませるよう指示するブロックがシステムプロンプトに入り、sed -iを止めてEditツールへ誘導するPreToolUseフックと矛盾します。この指示はA/B実験のフラグで出し分けられていて設定キーもドキュメントもなく、settings.jsonのenvにCLAUDE_CODE_THRIFTY_SONIC=0を置くと消えます。EditとWriteを前提にしたフックや/rewindが黙って空振りする構図と、--settingsで3通りを試した結果も書きます。
その決定、出自不明につき
設計判断をADRとして積む運用で、サブエージェントが「ユーザー決定」と書いて自分で起票したADRが混ざりました。本文を書き換えず撤回ADRを足して取り消し、ADRには誰が決めたかを書かせる規約を足した話です。
PreToolUseフックで代替手段まで返す
Claude Codeのpermissions.denyはブロックするだけで理由を返せないため、代替手段の探索をモデルに任せることになり、そのぶんトークンを使います。Bash向けのdenyをPreToolUseフックに移し、exit 2でstderrに書いた文を返すことで、ルールごとに止めた理由と代替手段を伝えられるようにしました。プレフィックス一致では見えなかった回避経路と、止める範囲を引き直した基準も書きます。
見つけさせて、直させない
PRのレビューで担保しきれずに漏れるものを拾うため、週次でコードベースを走査するだけのサブエージェントを立てています。修正はさせず検知と報告に限り、報告はレビュー側で裏を取る運用を1か月ほど回した記録です。
ターミナルにリマインダー権限を渡さずにMCPからEventKitを触る
Claude Desktop経由でMCPサーバーを起動するとReminders権限が通らない問題を、独立したLaunchAgentのdaemonへEventKitアクセスを集約して解決しました。結果として、ターミナルやClaude Desktopに権限を与えず、アプリ本体だけに絞れる構成になっています。
サブエージェントに報告ルールを守らせる
サブエージェントに計画と途中経過を報告させるルールの届け方を、hookで4通り試しました。同じ文章でも、ツールの結果に付いてくる形とCLAUDE.mdに書く形は埋め込み指示として退けられ、指示文の一部に混ぜる形と、ハーネスがサブエージェントに渡すコンテキストに入れる形だけが通っています。
Claude Codeの使用率ガードをTeamシートで止める
Claude Codeを任意の使用率で止めるガードについて、残していたTeamプレミアムシートでの確認を済ませ、UserPromptSubmitとPreToolUseをしきい値を下げて実際に発火させました。その過程で、セッション最初のレンダーがrate_limitsを持たないせいで前のセッションの観測値を消していたセンサー側の穴が見つかっています。
レイヤーの不透明度が効いていなかったのでUIImageのdraw(at:)を引数付きに変えた
ピクセルアート制作アプリSpriteFrameで、レイヤーの不透明度スライダーが表示・書き出し・サムネイルのどの合成結果にも反映されていませんでした。原因はUIImage.draw(at:)がCGContextのalpha設定を無視して常に不透明で描画することで、draw(at:blendMode:alpha:)に切り替えて直しました。
Apple向けアプリのCIを自前のMac 1台で回している
lintとテストはMac miniに置いたSelf-Hosted Runnerで走らせ、TestFlightとApp Store Connectへの配布はXcode Cloudに任せています。配布も同じランナーに載せようとして、署名鍵はランナーに置かないと決めました。そこに至るまでの経緯と、まっさらにならないマシンで踏んだ問題を振り返ります。
SVGのラスタライズをsipsとresvgとChromeで比べる
エージェントに書かせたSVGをPNGにして目視させる確認ループで、sipsだけ太字が出ませんでした。sipsがfont-weightを無視しているのではなく、boldキーワードとfont-familyの先頭が解決できない場合の2つで落ちていた話と、resvg・ヘッドレスChromeと比べた結果をまとめます。
ディレクトリごとにClaude Codeのアカウントを分ける
Claude Codeの設定ディレクトリは CLAUDE_CONFIG_DIR で切り替えられます。macOSは認証情報をKeychainに置くので空の設定ディレクトリでもログイン状態は引き継がれる、と公式ドキュメントにありますが、実際にはKeychainのサービス名に設定ディレクトリのパスのsha256が付くため引き継がれません。ディレクトリごとにアカウントを分ける運用と、その根拠を書きます。
Discordの音声E2EEプロトコルDAVEをSwiftで受信・復号する
Discordがボイスチャンネル向けに導入したE2EEプロトコルDAVEについて、ボットとしてMLSグループに参加し、他参加者の音声をRTP受信からOpusデコードまでSwiftネイティブで復号する検証をしました。公式ドキュメントだけでは埋まらない、受信側実装だけで見える3つの落とし穴を扱います。
DiscordのE2E暗号化DAVEをSwiftから使うためlibdaveのC APIを直接呼び出した
DiscordのE2E暗号化プロトコルDAVEに、Swiftから対応できるかを検証しました。Discord公式のC++実装libdaveはフラットなC APIを公開しているため、Swift/C++ interopの実験的機能を使わず、標準のC interopだけでセッションの作成から破棄まで動かせることを確認しています。
