TL;DR
| 項目 | 内容 |
|---|---|
| 🎯 対象 | VS Code拡張機能「Japanese Input for Claude Code」v1.7.4 |
| 😅 やらかし | Guardrails設定のインポート/エクスポートをクリップボードで実装 |
| 💥 問題1 | クリップボードが他の作業で上書きされて設定が消える |
| 💥 問題2 | インポート時に同名テンプレートが重複作成される |
| ✅ 解決 | ファイルベースに変更 + 名前ベースの重複検出&マージ |
🎬 はじめに
VS Code拡張機能「Japanese Input for Claude Code」の開発者です。
v1.7.1でGuardrails設定のインポート/エクスポート機能を追加したんですが、「クリップボード経由で手軽にやろう」 という安易な判断が見事に裏目に出ました。
実際の問題はクリップボード経由ってことではないんですが・・・。
今回は、その失敗と修正の記録です。
📋 Guardrails機能とは
Claude Code、Codex、Gemini Code Assistなど複数のAIコーディングアシスタントに対して、AI別のプリセット指示を自動挿入する機能です。
例: Claude Code用Guardrails
「日本語で回答してください。コードにはコメントを付けてください。」
この設定をチーム内で共有したい、PCを移行しても引き継けたらいいなということで、インポート/エクスポート機能を実装しました。
😅 v1.7.1: クリップボードで実装した理由
当初の設計思想:
💭 「JSONをクリップボードにコピー/ペーストするだけでいいじゃん」
💭 「ファイル保存ダイアログとか出すの面倒だし」
💭 「SlackやTeamsに貼り付けて共有できて便利!」
実装も簡単でした:
// エクスポート
await vscode.env.clipboard.writeText(JSON.stringify(templates, null, 2));
vscode.window.showInformationMessage('クリップボードにコピーしました');
// インポート
const clipboardText = await vscode.env.clipboard.readText();
const templates = JSON.parse(clipboardText);
10分で実装完了。完璧。リリース。
...と思っていた時期が私にもありました。
💥 問題1: クリップボードは信用できない
発生した事象
- エクスポートボタンをクリック(JSONがクリップボードにコピーされる)
- 「よし、別のPCに持っていこう」とSlackを開く
- 何か別のものをコピーしてしまう(URLとか、コードスニペットとか)
- 別PCでインポートしようとすると... さっきのJSONがない 😇
根本原因
クリップボードは「一時的な置き場所」であって「保存場所」ではない
当たり前のことなんですが、実装しているときは「すぐ貼り付けるでしょ」と楽観的に考えていました。
💥 問題2: 重複テンプレートが無限増殖
これが本当におバカなバグでした。
発生した事象
- Guardrails設定をエクスポート
- 設定を少し編集
- もう一度インポート
- 同じ名前のテンプレートが2つになる 🤦
Before:
- Claude Code (id: abc123)
After:
- Claude Code (id: abc123) ← 元のやつ
- Claude Code (id: xyz789) ← 新しく作られたやつ
根本原因
インポート時の処理がこうなっていた:
// ❌ ダメなコード
for (const template of importedTemplates) {
// IDで存在チェック
if (!existingTemplates.find(t => t.id === template.id)) {
existingTemplates.push(template); // 新規追加
}
}
エクスポート→インポートでIDが変わるので、名前が同じでも「新規テンプレート」として追加されてしまう。
「IDで重複チェック」という発想自体がおかしかった。ユーザーにとって同じものかどうかは「名前」で決まるのに。
✅ v1.7.3-4: 修正内容
修正1: ファイルベースに変更
// エクスポート: ファイル保存ダイアログ
const uri = await vscode.window.showSaveDialog({
defaultUri: vscode.Uri.file('guardrails-settings.json'),
filters: { 'JSON': ['json'] }
});
if (uri) {
await vscode.workspace.fs.writeFile(uri, Buffer.from(JSON.stringify(templates, null, 2)));
}
// インポート: ファイル選択ダイアログ
const uris = await vscode.window.showOpenDialog({
filters: { 'JSON': ['json'] }
});
if (uris && uris[0]) {
const content = await vscode.workspace.fs.readFile(uris[0]);
const templates = JSON.parse(content.toString());
}
メリット:
- 設定が永続化される
- 好きなタイミングでインポートできる
- Git管理してチーム共有も可能
修正2: 名前ベースの重複検出&マージ
// ✅ 修正後のコード
let added = 0, updated = 0, skipped = 0;
for (const imported of importedTemplates) {
const existing = existingTemplates.find(t => t.name === imported.name);
if (existing) {
// 同名テンプレートが存在 → 内容を更新
existing.content = imported.content;
existing.updatedAt = new Date().toISOString();
updated++;
} else {
// 新規テンプレート → 追加
existingTemplates.push({
...imported,
id: generateNewId() // 新しいIDを発行
});
added++;
}
}
// 結果をフィードバック
vscode.window.showInformationMessage(
`インポート完了: 追加 ${added}件, 更新 ${updated}件`
);
ポイント:
- 名前で一致判定(IDではなく)
- 既存テンプレートは上書き更新
- 新規テンプレートのみ追加
- 処理結果を詳細表示
📊 Before / After
| 項目 | v1.7.1 (Before) | v1.7.4 (After) |
|---|---|---|
| 方式 | クリップボード | ファイル保存/読込 |
| 永続性 | ❌ 上書きで消える | ✅ ファイルとして保存 |
| 重複処理 | ❌ IDで判定→重複発生 | ✅ 名前で判定→マージ |
| フィードバック | 「コピーしました」のみ | 追加/更新件数を表示 |
| 共有方法 | Slack等に貼り付け | ファイルを送付 or Git管理 |
🎓 学び
1. クリップボードを「保存先」にするな
クリップボードは「転送経路」であって「保存場所」ではない。設定のエクスポートのように後で使うかもしれないデータは、ファイルに保存すべき。
2. 重複判定は「ユーザー視点」で考える
内部IDで判定するのはエンジニア視点。ユーザーにとっての「同じもの」は何かを考えれば、名前で判定するのが自然。
3. 処理結果は具体的に伝える
「完了しました」だけでなく「追加 2件, 更新 1件」のように何が起きたかを伝えると、ユーザーは安心できる。
おわりに
書いててアホ過ぎて穴に逃げ込みたくなるんですが・・。
「簡単に実装できる」と「ユーザーにとって使いやすい」は別物ですね。
クリップボードでサクッと実装→2日後にファイルベースに書き直し、という無駄な工数を発生させてしまいました。最初から「設定の永続化」という要件を真面目に考えていれば...。
同じ轍を踏む人が減れば幸いです 🙏