## 経緯
2025年6月に[[Claude Code]]デビューをしたが、当時より性能が劣化したりヨイショが嫌にやって9月にアンインストールをした。
しかし、年末頃から[[Claude Code]]の評判がまた良くなり、年明けには[[Anthropic]]の知名度向上も相まって、[[エージェント]]は[[Claude]]一色になっていた。
仕事のうえでは[[GitHub Copilot CLI]]だし、プライベートは[[Codex CLI]]で満足しているものの、このままでは[[ハーネスエンジニアリング]]はできても[[ダークソフトウェアファクトリー]]の域には到達できないという自覚がある。せっかくなら高みを目指したいので再契約して使ってみることにした。
### 環境
| 対象 | バージョン |
| --------------- | ------ |
| [[macOS]] | 15.7.4 |
| [[Claude Code]] | 2.1.76 |
| [[cmux]] | 0.62.2 |
## インストール
公式の推奨は `curl` だが、[[Homebrew]]にも対応しているのでこちらを使う。
```console
brew install --cask claude-code
```
## 設定の不備を修正
### [[Context7 MCP Server]] が使えない
```
context7: https://mcp.context7.com/sse (SSE) - ✗ Failed to connect
```
`https://mcp.context7.com/sse` は非推奨で `https://mcp.context7.com/mcp` を使うべき。
```
claude mcp remove context7 -s user
claude mcp remove deepwiki -s user
npx ctx7 setup --claude
```
`CLI + Skills` の方を選ぶ。[[OAuth]]が必要だったので [[GitHub]] を選択。
### 通知
以下の設定は一旦消してみる。
```lua
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"タスクが完了しました\" with title \"Claude Code\" subtitle \"処理終了\" sound name \"Hero\"'"
}
]
}
]
}
}
```
## 通知がこない
[[cmux]]で通知バッジが光らない。
`Claude Code Integration` を有効にしてみた。(デフォルト有効だけど、以前は使っていなかったので無効にしていた)
![[2026-03-15-13-06-11.avif]]
ユーザー確認待ちは光るようになったけど、なんか挙動がよく分からんので自分で設定したほうが良さそうなので設定した。
<div class="link-card-v2">
<div class="link-card-v2-site">
<img class="link-card-v2-site-icon" src="https://publish-01.obsidian.md/access/35d05cd1bf5cc500e11cc8ba57daaf88/favicon-64.png" />
<span class="link-card-v2-site-name">Minerva</span>
</div>
<div class="link-card-v2-title">
📜2026-03-15 Claude Codeで人間が確認待ちの状態になっている場合に通知させる
</div>
<div class="link-card-v2-content">cmux notifyの不安定さから自前通知実装に切り替え、Claude Codeのhooksを~/.claude/settings.jsonに定義した。idle_promptとStopイベントをnotify.shで受け取り、OSC 777シーケンスを/dev/ttyへ出力して入力待ちと応答完了の通知を表示した。</div>
<img class="link-card-v2-image" src="https://publish-01.obsidian.md/access/35d05cd1bf5cc500e11cc8ba57daaf88/Notes/attachments/activity.webp" />
<a data-href="📜2026-03-15 Claude Codeで人間が確認待ちの状態になっている場合に通知させる" class="internal-link"></a>
</div>
%%[[📜2026-03-15 Claude Codeで人間が確認待ちの状態になっている場合に通知させる]]%%
## [[ハーネス]]をつくる
### [[フック (Claude Code)|フック]]の定義場所
変更が入ったらどうするかの定義ファイル。`settings.json`の場所は以下のように使い分ける。
| スコープ | ファイル | チーム共有 | 用途 |
| ----------- | ----------------------------- | ----------- | ---------------- |
| **Project** | `.claude/settings.json` | ✅ git管理可 | チーム全体に強制したい品質ゲート |
| **Local** | `.claude/settings.local.json` | ❌ gitignore | 個人のみの実験的なhooks |
| **User** | `~/.claude/settings.json` | ❌ ローカルのみ | 全プロジェクト共通の個人ルール |
> [[フックのファイル定義場所 (Claude Code)|フックのファイル定義場所]]
とりあえずはカレントディレクトリで試したいので `.claude/settings.json` を使う。
`package.json`
```json
{
"name": "nuxt4-sandbox",
"type": "module",
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"typecheck": "golar -b --noEmit",
"postinstall": "nuxt prepare",
"format:fix": "prettier --write --ignore-unknown ."
},
"dependencies": {
"nuxt": "^4.3.1",
"vue": "^3.5.28",
"vue-router": "^4.6.4"
},
"optionalDependencies": {
"typescript": "5.9.3"
},
"devDependencies": {
"@fsouza/prettierd": "0.27.0",
"@golar/vue": "0.0.13",
"golar": "0.0.13",
"prettier-plugin-organize-imports": "4.3.0"
}
}
```
#### 対象リポジトリ
[[Nuxt4]]動作確認用sandboxプロジェクトで行う。
### 自動フォーマット
まずはファイルが変更されたら自動フォーマットされるようにする。ただ、フォーマットで挙動が変わることは基本ないはずなので、最後に一度だけすれば良さそう。
今後は[[Biome]]や[[Oxfmt]]に移行する可能性が高く、フォーマットは全体でも大した問題にならない。ので全体にかける。
`package.json`
```json
{
"scripts": {
"format:fix": "prettier --write --ignore-unknown ."
},
}
```
`.claude/settings.json`
```json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bun run format:fix"
}
]
}
]
}
}
```
### 自動型チェック
次は型チェックを行う。既に `bun run typecheck` があるのでそのまま使える。`exit code` は `2` なので注意。
| exit code | 挙動 |
| ----------- | ------------------------------------------------------- |
| `0` | 正常。Claudeのターン終了を許可 |
| `2` | ブロック。**stderrの内容がClaudeへのフィードバックとして渡され**、Claudeは作業を継続する |
| `それ以外(1など)` | エラーとして扱われるが、ターン終了は許可。stderrはログに残るが**Claudeには渡されない** |
> The exit code determines what happens next:
>
> - **Exit 0**: the action proceeds. For `UserPromptSubmit` and `SessionStart` hooks, anything you write to stdout is added to Claude’s context.
> - **Exit 2**: the action is blocked. Write a reason to stderr, and Claude receives it as feedback so it can adjust.
> - **Any other exit code**: the action proceeds. Stderr is logged but not shown to Claude. Toggle verbose mode with `Ctrl+O` to see these messages in the transcript.
>
> *[Automate workflows with hooks - Claude Code Docs](https://code.claude.com/docs/en/hooks-guide#agent-based-hooks) *
型チェックもファイル1つ変更ごとにチェックしても無駄......というか1ファイルずつだと100%型エラーになる瞬間がある。よって、formatと同じタイミングで実行する。
`settings.json`
```json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bun run format:fix"
},
{
"type": "command",
"command": "bun run typecheck >&2 || exit 2"
}
]
}
]
}
}
```
未使用変数・関数をそのままにしたくないので
`nuxt.config.ts`
```ts
export default defineNuxtConfig({
compatibilityDate: "2025-07-15",
devtools: { enabled: true },
typescript: {
tsConfig: {
compilerOptions: {
noUnusedLocals: true,
noUnusedParameters: true,
},
},
},
});
```
[[tsconfig.json]]を再生成するため `nuxt prepare` も忘れずに。
```console
nuxt prepare
```
### 自動テスト
[[Vitest]]を入れる。
```console
bun add -D vitest
```
`package.json`
```json
{
"scripts": {
"test": "vitest run"
}
"devDependencies": {
"vitest": "4.0.18"
}
}
```
`.claude/settings.json`
```json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bun run format:fix"
},
{
"type": "command",
"command": "bun run typecheck >&2 || exit 2"
},
{
"type": "command",
"command": "bun run test >&2 || exit 2"
}
]
}
]
}
}
```
### 自動リント
[[Oxlint]]を入れる。
```console
bun add -d oxlint
```
`package.json`
```json
{
"scripts": {
"lint": "oxlint --vue-plugin"
},
"devDependencies": {
"oxlint": "1.51.0",
}
}
```
`.claude/settings.json`
```json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bun run format:fix"
},
{
"type": "command",
"command": "bun run typecheck >&2 || exit 2"
},
{
"type": "command",
"command": "bun run lint >&2 || exit 2"
},
{
"type": "command",
"command": "bun run test >&2 || exit 2"
}
]
}
]
}
}
```
### 並列実行について
> [!left-bubble] ![[claudine-mini.webp]] **[[クロディーヌ]]**
> **背景**: Stopフックに4コマンド(format:fix, typecheck, lint, test)があり、直列実行で約8秒かかる。
>
> **並列化の方法**:
> - Stopイベントに複数のmatcherオブジェクトを定義すると並列実行される
> - またはシェルで`&`+`wait`を使う方法もある
>
> **課題**:
> - `format:fix`はファイルを書き換えるため、他チェックと並列だと競合リスクあり
> - 並列実行するとstderrの出力が混在する可能性がある
>
> **結論**: 直列のまま維持。並列化しても8s→2s程度の改善で、ターン終了時の6秒差は実用上誤差。出力の確実性を優先した。
## リントのカスタマイズ
`vue` から不要な明示的[[import (ESM)|import]]をした場合にエラーにしてみる。
こんな風に設定を追加すると...
`.oxlintrc.json`
```json
{
"rules": {
"no-restricted-imports": [
"error",
{
"paths": [
{
"name": "vue",
"message": "Nuxtが自動インポートするため、明示的なimportは不要です。"
},
{
"name": "vue-router",
"message": "Nuxtが自動インポートするため、明示的なimportは不要です。"
}
]
}
]
}
}
```
`app.vue` がこうなっていたら
```html
<script setup lang="ts">
import { ref } from "vue";
const dummy = ref(0);
</script>
```
こんな風にエラーになる。
```error
× eslint(no-restricted-imports): 'vue' import is restricted from being used.
╭─[app/app.vue:2:1]
1 │ <script setup lang="ts">
2 │ import { ref } from "vue";
· ──────────────────────────
3 │ const dummy = ref(0);
╰────
help: Nuxtが自動インポートするため、明示的なimportは不要です。
Found 0 warnings and 1 error.
Finished in 5ms on 5 files with 102 rules using 12 threads.
error: script "lint" exited with code 1
```
[[Claude Code]]に頼めば設定ルールは勝手に作ってくれるので楽。