Repository navigation
feat: add inter-session communication inspired by Clawdbot - #15
Conversation
|
Caution Review failedThe pull request is closed. Walkthroughセッション間ブロードキャスト/受信トレイ機能のドキュメントと Bash ユーティリティ、Pre/Post フックの追加、MCP サーバー(session/status/workflow ツール群)実装、関連ファイル検証スキル設計、Webhook/GitHub Actions 設定ドキュメント、及びバージョン・CHANGELOG 更新を追加しました。(50語以内) Changes
Sequence Diagram(s)sequenceDiagram
participant User as ユーザー
participant Tool as ツール
participant PreHook as pretooluse-inbox-check
participant Inbox as session-inbox-check
participant Storage as .claude/sessions
participant PostHook as session-auto-broadcast
participant Broadcast as session-broadcast.sh
User->>Tool: ツール起動
Tool->>PreHook: PreToolUse フック呼び出し
PreHook->>Storage: 最終チェック時刻確認 (.last_inbox_check)
alt クールダウン内
PreHook-->>Tool: 最小出力で戻る
else
PreHook->>Inbox: 未読数取得 (--count / --hook)
Inbox->>Storage: broadcast.md と last-read を参照
Inbox-->>PreHook: 未読数と要約を返す
PreHook->>Tool: 未読通知(フック出力)
PreHook->>Storage: .last_inbox_check 更新
end
Tool->>Tool: 本体処理(例:ファイル編集)
Tool->>PostHook: PostToolUse フック呼び出し(file path を含む)
PostHook->>Storage: auto-broadcast.json を読み込みパターン照合
alt パターン一致
PostHook->>Broadcast: `session-broadcast.sh --auto <file> <msg>` 実行
Broadcast->>Storage: broadcast.md に追記(古い項目剪定)
Broadcast-->>PostHook: フック用 JSON 出力
PostHook-->>Tool: ブロードキャスト完了出力
else
PostHook-->>Tool: 空出力(何もしない)
end
Tool-->>User: 実行完了
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@commands/optional/session-inbox.md`:
- Around line 34-36: MD040 is triggered because the fenced code blocks for the
snippets are missing a language tag; update the two fences containing the lines
`/session-inbox --mark` and `bash
"${CLAUDE_PLUGIN_ROOT}/scripts/session-inbox-check.sh"` to use a bash language
specifier (i.e., change the opening backticks to ```bash) so both code fences
are explicitly marked as bash.
In `@scripts/pretooluse-inbox-check.sh`:
- Around line 60-68: The script builds INBOX_SUMMARY and ESCAPED_SUMMARY but the
JSON emitted by the heredoc uses only ${UNREAD_COUNT}, so the unread-summary is
never included; update the emitted JSON (the heredoc that writes
hookSpecificOutput.additionalContext) to include ESCAPED_SUMMARY (or
INBOX_SUMMARY) in the additionalContext value (escaping as appropriate), or if
you decide not to include a summary remove the INBOX_SUMMARY/ESCAPED_SUMMARY
generation lines to avoid dead code; refer to the variables INBOX_SUMMARY and
ESCAPED_SUMMARY and the heredoc that currently prints hookSpecificOutput to
locate and fix the code.
In `@scripts/session-inbox-check.sh`:
- Around line 22-28: The get_session_id function falls back to "unknown" when jq
is missing, which differs from session-broadcast.sh's behavior of returning a
generated session name like session-$(date +%s); update get_session_id to match
by returning the same generated session string when jq isn't available or
SESSION_FILE doesn't exist, mirroring session-broadcast.sh logic so session
identifiers remain consistent across scripts (adjust the get_session_id function
to emit the session-$(date +%s) fallback instead of "unknown").
🧹 Nitpick comments (9)
commands/optional/webhook-setup.md (1)
209-209: 裸の URL をマークダウンリンクに変換することを検討してください。URL をマークダウンリンク形式
[text](url)で囲むと、アクセシビリティと一貫性が向上します。📝 修正案
-> 💡 API キーは https://console.anthropic.com で取得できます +> 💡 API キーは [Anthropic Console](https://console.anthropic.com) で取得できますscripts/session-list.sh (4)
12-12: 未使用の変数SCRIPT_DIRがあります。この変数は定義されていますが、スクリプト内で使用されていません。削除するか、将来の使用予定があればコメントで説明を追加してください。
🧹 削除する場合の修正案
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
81-83: 未使用の変数active_count、stale_count、およびtmp_fileがあります。これらの変数は初期化されていますが、使用されていません。将来の機能(セッションのクリーンアップなど)のためのプレースホルダーであれば、TODO コメントを追加することを検討してください。
🧹 修正案
- local active_count=0 - local stale_count=0 - local tmp_file=$(mktemp) + # TODO: セッションのクリーンアップ機能実装時に使用 + # local active_count=0 + # local stale_count=0
89-113: パイプ内の while ループはサブシェルで実行されるため、ループ内で更新された変数は外部に反映されません。
active_countやstale_countを使用する予定がある場合、プロセス置換 (< <(...)) を使用するか、一時ファイル経由で処理する必要があります。また、status変数は抽出されていますが使用されていません。♻️ プロセス置換を使用した修正案
- jq -r 'to_entries[] | "\(.key)|\(.value.short_id)|\(.value.last_seen)|\(.value.status)"' "$ACTIVE_FILE" 2>/dev/null | while IFS='|' read -r full_id short_id last_seen status; do + while IFS='|' read -r full_id short_id last_seen _status; do local age=$((current_time - last_seen)) # ... rest of the loop - done + done < <(jq -r 'to_entries[] | "\(.key)|\(.value.short_id)|\(.value.last_seen)|\(.value.status)"' "$ACTIVE_FILE" 2>/dev/null)
37-38:local宣言と代入を分離して、コマンドの終了ステータスをマスクしないようにしてください。
local var=$(cmd)は常に成功を返すため、コマンドの失敗を検出できません。📝 修正案
- local current_session=$(get_current_session_id) - local current_time=$(get_current_timestamp) + local current_session + current_session=$(get_current_session_id) + local current_time + current_time=$(get_current_timestamp)scripts/session-broadcast.sh (2)
13-13: 未使用の変数SCRIPT_DIRがあります。他のスクリプト(session-list.sh など)と同様に、この変数は定義されていますが使用されていません。
79-80:local宣言と代入を分離してください。Shellcheck SC2155: コマンドの終了ステータスがマスクされる可能性があります。
📝 修正案
- local session_id=$(get_session_id) - local timestamp=$(get_timestamp) + local session_id + session_id=$(get_session_id) + local timestamp + timestamp=$(get_timestamp)scripts/session-inbox-check.sh (1)
148-156: JSON エスケープ処理が複雑で、特殊文字を含むメッセージで問題が発生する可能性があります。現在の
sedパイプラインは改行とクォートを処理していますが、バックスラッシュやその他の特殊文字(タブ、制御文字など)が含まれる場合に JSON が壊れる可能性があります。♻️ jq を使用した安全なエスケープ案
if [ "$unread_count" -gt 0 ]; then - local escaped_messages=$(echo -e "$unread_messages" | sed 's/\\/\\\\/g; s/"/\\"/g; s/$/\\n/' | tr -d '\n' | sed 's/\\n$//') + local escaped_messages + escaped_messages=$(echo -e "$unread_messages" | jq -Rs '.' | sed 's/^"//; s/"$//') cat <<EOFscripts/session-auto-broadcast.sh (1)
75-85: カスタムパターンの読み取りロジックを改善できます。
jqの出力が空の場合、while readループは1回実行されて空文字列を処理します。-n "$pattern"チェックで保護されていますが、より明示的なハンドリングが可能です。📝 改善案
if [ "$should_broadcast" = "false" ] && [ -f "$CONFIG_FILE" ] && command -v jq >/dev/null 2>&1; then - CUSTOM_PATTERNS=$(jq -r '.patterns // [] | .[]' "$CONFIG_FILE" 2>/dev/null) + CUSTOM_PATTERNS=$(jq -r '.patterns // [] | .[]' "$CONFIG_FILE" 2>/dev/null || true) + if [ -n "$CUSTOM_PATTERNS" ]; then while IFS= read -r pattern; do if [ -n "$pattern" ] && [[ "$FILE_PATH" == *"$pattern"* ]]; then should_broadcast="true" matched_pattern="$pattern" break fi done <<< "$CUSTOM_PATTERNS" + fi fi
| # 未読メッセージの概要を取得 | ||
| INBOX_SUMMARY=$(bash "$SCRIPT_DIR/session-inbox-check.sh" 2>/dev/null | head -5 || echo "") | ||
|
|
||
| # エスケープ処理 | ||
| ESCAPED_SUMMARY=$(echo "$INBOX_SUMMARY" | sed 's/\\/\\\\/g; s/"/\\"/g; s/$/\\n/' | tr -d '\n' | sed 's/\\n$//') | ||
|
|
||
| cat <<EOF | ||
| {"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":"📨 他セッションからの未読メッセージが ${UNREAD_COUNT}件 あります。\\n/session-inbox で確認してください。"}} | ||
| EOF |
There was a problem hiding this comment.
未読概要が通知に反映されていません
Line 60-65 で概要を生成していますが、Line 66-68 の出力に使われていません。通知に要約を含めるか、不要なら生成処理を削除してください。
🛠️ 追加Contextに概要を含める例
- cat <<EOF
-{"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":"📨 他セッションからの未読メッセージが ${UNREAD_COUNT}件 あります。\\n/session-inbox で確認してください。"}}
-EOF
+ cat <<EOF
+{"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":"📨 他セッションからの未読メッセージが ${UNREAD_COUNT}件 あります。\\n/session-inbox で確認してください。\\n${ESCAPED_SUMMARY}"}}
+EOF📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # 未読メッセージの概要を取得 | |
| INBOX_SUMMARY=$(bash "$SCRIPT_DIR/session-inbox-check.sh" 2>/dev/null | head -5 || echo "") | |
| # エスケープ処理 | |
| ESCAPED_SUMMARY=$(echo "$INBOX_SUMMARY" | sed 's/\\/\\\\/g; s/"/\\"/g; s/$/\\n/' | tr -d '\n' | sed 's/\\n$//') | |
| cat <<EOF | |
| {"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":"📨 他セッションからの未読メッセージが ${UNREAD_COUNT}件 あります。\\n/session-inbox で確認してください。"}} | |
| EOF | |
| # 未読メッセージの概要を取得 | |
| INBOX_SUMMARY=$(bash "$SCRIPT_DIR/session-inbox-check.sh" 2>/dev/null | head -5 || echo "") | |
| # エスケープ処理 | |
| ESCAPED_SUMMARY=$(echo "$INBOX_SUMMARY" | sed 's/\\/\\\\/g; s/"/\\"/g; s/$/\\n/' | tr -d '\n' | sed 's/\\n$//') | |
| cat <<EOF | |
| {"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":"📨 他セッションからの未読メッセージが ${UNREAD_COUNT}件 あります。\\n/session-inbox で確認してください。\\n${ESCAPED_SUMMARY}"}} | |
| EOF |
🧰 Tools
🪛 Shellcheck (0.11.0)
[warning] 64-64: ESCAPED_SUMMARY appears unused. Verify use (or export if used externally).
(SC2034)
🤖 Prompt for AI Agents
In `@scripts/pretooluse-inbox-check.sh` around lines 60 - 68, The script builds
INBOX_SUMMARY and ESCAPED_SUMMARY but the JSON emitted by the heredoc uses only
${UNREAD_COUNT}, so the unread-summary is never included; update the emitted
JSON (the heredoc that writes hookSpecificOutput.additionalContext) to include
ESCAPED_SUMMARY (or INBOX_SUMMARY) in the additionalContext value (escaping as
appropriate), or if you decide not to include a summary remove the
INBOX_SUMMARY/ESCAPED_SUMMARY generation lines to avoid dead code; refer to the
variables INBOX_SUMMARY and ESCAPED_SUMMARY and the heredoc that currently
prints hookSpecificOutput to locate and fix the code.
Implemented features learned from Clawdbot competitor analysis: Session Communication (Phase 1): - /session-broadcast: Send messages to all sessions - /session-inbox: Check received messages - /session-list: Show active sessions Hooks Integration: - Auto inbox check before Write/Edit (5-min interval) - Auto broadcast on API/type file changes External Automation (Phase 2): - /webhook-setup: GitHub Actions integration for auto-review Future Design (Phase 4): - E2E verification design document (CDP/Playwright) This maintains Harness's lightweight plugin philosophy while adding real-time-ish session awareness capabilities. https://claude.ai/code/session_01GgwkKJXFZpZWdh1etQbvaw
Add a new verification step that automatically checks for missed file updates after implementation: - Detect function signature changes → warn about unchecked callers - Detect interface/type changes → warn about implementation inconsistencies - Detect export changes → warn about broken imports - Detect config changes → warn about unsynchronized related configs Integrated into /work flow: - Phase 1: Related-check added to task-worker self-review loop - Phase 3: Final related files verification before commit Co-Authored-By: Claude <noreply@anthropic.com>
Enables Claude Code, Codex, and Cursor to share sessions via MCP protocol. MCP Server (mcp-server/): - Session tools: list, broadcast, inbox, register - Workflow tools: plan, work, review - Status tools: project status New Commands: - /mcp-setup: Configure MCP for different clients This allows seamless collaboration between different AI clients working on the same project, with real-time message passing. https://claude.ai/code/session_01GgwkKJXFZpZWdh1etQbvaw
3ddcdc2 to
7e17f40
Compare
Merged features: - Automatic detection of missed updates after implementation - Function signature change detection - Interface/type change warnings - Export modification alerts - Config change verification Integrated into /work workflow (Phase 1 and Phase 3) https://claude.ai/code/session_01GgwkKJXFZpZWdh1etQbvaw
Added documentation for: - Inter-session communication (/session-broadcast, /session-inbox, /session-list) - MCP Server for cross-client support (Claude Code, Codex, Cursor) - Webhook automation (/webhook-setup) - E2E verification design Updated both English and Japanese versions. https://claude.ai/code/session_01GgwkKJXFZpZWdh1etQbvaw
There was a problem hiding this comment.
Actionable comments posted: 8
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🤖 Fix all issues with AI agents
In `@mcp-server/src/tools/session.ts`:
- Around line 280-282: Assigning sessionId/client to process.env
(process.env.HARNESS_SESSION_ID, process.env.HARNESS_CLIENT) inside
handleRegister mutates global state for the entire MCP server; stop using
process.env for request-scoped session data and instead store the values on a
request-scoped context object or pass them through the call chain. Replace
writes to process.env in handleRegister with a per-request Context (e.g.,
sessionContext or a property on the incoming request object) and update any
consumers to read from that context (or accept sessionId/client as parameters)
so different requests/sessions don't clobber each other.
- Around line 103-118: The loadSessions/saveSessions pair has a race where
concurrent saveSessions() calls can overwrite each other; modify saveSessions to
perform an atomic write by writing JSON to a temporary file in the same
directory (e.g., ACTIVE_FILE + ".tmp" or using a unique suffix), fsync the temp
file (or file descriptor), then rename/replace the temp to ACTIVE_FILE
(fs.renameSync) to ensure atomic replace; keep loadSessions() unchanged but
ensure it reads ACTIVE_FILE only after rename so it never sees a
partially-written file; alternatively, if you prefer locking, add an exclusive
lock around loadSessions/saveSessions (e.g., using a simple advisory lock or a
small lockfile) to serialize access to ACTIVE_FILE.
- Around line 27-31: SESSIONS_DIR and derived constants (ACTIVE_FILE,
BROADCAST_FILE) use a raw relative path; replace them to resolve against the
project root using getProjectRoot() (as done in status.ts/workflow.ts) and
path.join so paths work regardless of process.cwd(); import getProjectRoot and
path, set SESSIONS_DIR = path.join(getProjectRoot(), ".claude", "sessions") and
build ACTIVE_FILE/BROADCAST_FILE from that SESSIONS_DIR.
In `@mcp-server/src/tools/status.ts`:
- Around line 31-45: getProjectRoot currently uses `while (current !== "/")`
which hangs on Windows because `path.dirname("C:\\") === "C:\\"`; change the
loop to detect root correctly (e.g., compute const root =
path.parse(current).root and stop when current === root or when
path.dirname(current) === current) and return the last checked directory if no
marker found; update references to the function (notably the duplicate in
workflow.ts) by extracting getProjectRoot into a shared utility module and
importing it from both places to remove duplication. Ensure you reference and
update the function named getProjectRoot and the duplicate implementation in
workflow.ts when making these changes.
In `@mcp-server/src/tools/workflow.ts`:
- Around line 86-101: getProjectRoot is duplicated in workflow.ts and status.ts
and uses a Unix-only loop termination (current !== "/"); extract a single
exported getProjectRoot into a common utility module, replace both local
definitions with imports, and remove the duplicates. Implement the termination
using path.parse(current).root (or comparing current === path.dirname(current))
so it works on Windows, keep the same marker list and fs.existsSync logic, and
update any references in workflow.ts and status.ts to call the shared
getProjectRoot.
In `@scripts/session-auto-broadcast.sh`:
- Around line 96-98: The hook JSON currently interpolates FILE_NAME directly
into a here-doc (cat <<EOF ...) which can break if FILE_NAME contains quotes or
backslashes; replace the heredoc JSON construction with a safe JSON generator
using jq -n --arg to pass FILE_NAME (e.g. use jq -n --arg fileName "$FILE_NAME"
'{hookSpecificOutput: {hookEventName:"AutoBroadcast", additionalContext: ("📢
自動ブロードキャスト: " + $fileName + " の変更を他セッションに通知しました")}}' ) and emit that output
instead of the raw heredoc so the FILE_NAME is properly escaped.
In `@scripts/session-broadcast.sh`:
- Around line 90-109: Wrap the append and pruning sequence that writes to
BROADCAST_FILE (the echo "## ${timestamp} ..." block, the msg_count grep, mktemp
creation, awk filtering and mv) inside a file lock; detect and prefer flock when
available (e.g. if command -v flock) and otherwise use an exclusive lock via a
dedicated lock fd (exec 200>"${BROADCAST_FILE}.lock"; flock 200 ...; exec
200>&-). Ensure mktemp and temp_file creation/cleanup and the mv into
BROADCAST_FILE happen while the lock is held, and release the lock after
completion; add a trap to remove temp_file on exit/error to avoid stale temp
files. This will protect BROADCAST_FILE and the MAX_MESSAGES pruning logic from
concurrent runs.
- Around line 116-119: The current here-doc emits unescaped JSON using the raw
${message:0:50}, breaking when message contains " or \; replace the heredoc
block that prints hookSpecificOutput with a jq-based generator (use jq -n --arg
truncated "$(echo "${message}" | cut -c1-50)" '{hookSpecificOutput:
{hookEventName:"Broadcast", additionalContext: ("📤 ブロードキャスト送信: " +
$truncated)}}') so jq handles proper JSON escaping; update the branch that
checks HOOK_OUTPUT to call jq (or fail/emit a safe fallback if jq is
unavailable) and reference the HOOK_OUTPUT check and the message variable in the
change.
♻️ Duplicate comments (2)
scripts/pretooluse-inbox-check.sh (1)
60-68:ESCAPED_SUMMARYが生成されているが出力に含まれていませんLine 64 で
ESCAPED_SUMMARYを生成していますが、Line 66-68 の JSON 出力には使用されていません。概要を通知に含めるか、不要であれば生成処理を削除してください。scripts/session-inbox-check.sh (1)
22-28:get_session_idのフォールバックがsession-broadcast.shと一貫性がありません
session-broadcast.shでは jq がない場合session-$(date +%s)を返しますが、このスクリプトではunknownを返しています。この不一致により、jq がインストールされていない環境でセッション追跡に問題が発生する可能性があります。🔧 一貫性のある修正案
get_session_id() { if [ -f "$SESSION_FILE" ] && command -v jq >/dev/null 2>&1; then jq -r '.session_id // "unknown"' "$SESSION_FILE" 2>/dev/null else - echo "unknown" + echo "session-$(date +%s)" fi }関連コード:
scripts/session-broadcast.shの 21-27 行目を参照してください。
🟡 Minor comments (16)
commands/optional/webhook-setup.md-209-209 (1)
209-209: 裸URLをリンク表記にしてくださいMD034 に抵触します。
🛠 変更案
-> 💡 API キーは https://console.anthropic.com で取得できます +> 💡 API キーは <https://console.anthropic.com> で取得できますdocs/E2E_VERIFICATION_DESIGN.md-66-72 (1)
66-72: ベースURLは裸リンクではなくコード/リンク表記にしてくださいMD034 に抵触します。
🛠 変更案
-| `--base-url` | 対象URL | http://localhost:3000 | +| `--base-url` | 対象URL | `http://localhost:3000` |mcp-server/README.md-93-115 (1)
93-115: フェンス付きブロックに言語指定を追加してください図とツリーのフェンスが MD040 に抵触しています。
🛠 変更案
-``` +```text [Claude Code] [Codex] ...-
+text
mcp-server/
├── src/
...
└── README.mdAlso applies to: 135-146
commands/optional/mcp-setup.md-45-45 (1)
45-45: 強調ではなく見出しにしてください見出し扱いの文が強調になっており、MD036 に抵触します。
🛠 変更案
-**ユーザーの回答を待つ** +#### ユーザーの回答を待つcommands/optional/webhook-setup.md-44-44 (1)
44-44: 強調ではなく見出しにしてくださいMD036 に抵触します。
🛠 変更案
-**ユーザーの回答を待つ** +#### ユーザーの回答を待つcommands/optional/mcp-setup.md-134-153 (1)
134-153: コードブロックに言語指定を追加してくださいフェンスに言語がなく MD040 に抵触します。
🛠 変更案
-``` +```text [Claude Code] You: harness_session_register を実行して、client: "claude-code" ... You: OK、新しい API を使って実装を続けて</details> </blockquote></details> <details> <summary>docs/E2E_VERIFICATION_DESIGN.md-15-44 (1)</summary><blockquote> `15-44`: **フェンス付きブロックに言語指定を追加してください** MD040 に抵触しています。 <details> <summary>🛠 変更案</summary> ```diff -``` +```text ┌─────────────────────────────────────────────────────────┐ ... └───────────┘-
+text
.claude/e2e/
├── baseline/
...
└── diff/-``` +```text /verify ├── build ... └── e2e</details> Also applies to: 113-123, 143-149 </blockquote></details> <details> <summary>commands/optional/session-broadcast.md-34-52 (1)</summary><blockquote> `34-52`: **フェンス付きブロックに言語指定を追加してください** MD040 に抵触しています。 <details> <summary>🛠 変更案</summary> ```diff -``` +```text 「他のセッションに、API変更したことを伝えて」 → ブロードキャストメッセージを送信-
+text
{ユーザーのメッセージ}CHANGELOG.md-46-51 (1)
46-51: CHANGELOG に今回の PR の主要機能(セッション間通信)が記載されていません。追加された
verify-related-files機能は記載されていますが、PR の主題である以下の機能が CHANGELOG に含まれていません:
/session-broadcast,/session-inbox,/session-listコマンド- MCP サーバーによるセッション通信機能
- Hooks 統合(PreToolUse inbox check, PostToolUse auto-broadcast)
マージ前に CHANGELOG を更新することを推奨します。
mcp-server/src/tools/status.ts-125-128 (1)
125-128:argsがundefinedの場合にデストラクチャリングエラーが発生します。
handleStatusToolからargsがundefinedで渡される可能性がありますが、handleStatusで直接デストラクチャリングするとエラーになります。🐛 修正案
function handleStatus(args: { verbose?: boolean }): { content: Array<{ type: string; text: string }>; } { - const { verbose = false } = args; + const { verbose = false } = args || {};mcp-server/src/tools/workflow.ts-111-121 (1)
111-121:execSyncは git がない環境や最初のコミット前に例外を投げます。現在の
catchブロックは空配列を返しますが、エラーの種類(git 未インストール vs HEAD~1 不在 vs その他)を区別できません。HEAD~1が存在しない場合はgit diff --staged --name-onlyなど代替手段を検討してください。♻️ より堅牢な実装案
function getRecentChanges(): string[] { try { - const output = execSync("git diff --name-only HEAD~1", { + // Try HEAD~1 first, fallback to staged changes + let output: string; + try { + output = execSync("git diff --name-only HEAD~1", { + encoding: "utf-8", + cwd: getProjectRoot(), + stdio: ['pipe', 'pipe', 'pipe'], + }); + } catch { + output = execSync("git diff --staged --name-only", { + encoding: "utf-8", + cwd: getProjectRoot(), + stdio: ['pipe', 'pipe', 'pipe'], + }); + } - encoding: "utf-8", - cwd: getProjectRoot(), - }); return output.trim().split("\n").filter(Boolean); } catch { return []; } }mcp-server/src/tools/status.ts-47-59 (1)
47-59:getPlansStatus()はPlansParserと異なるマーカー形式でカウントしており、状態計算に不整合があります。
getPlansStatus()はcc:TODO、cc:WIP、cc:DONEのテキスト出現回数をカウントしていますが、PlansParserはチェックボックス(- [ ],- [x])とバッククォート内のマーカー(`cc:TODO`など)を構文的に解析しています。さらに、実際の Plans.md では
cc:DONEは使用されておらず、代わりにcc:完了またはチェックボックス[x]で完了を表現しているため、getPlansStatus()の "done" カウントは常にゼロになります。例えば
- [x] タスク cc:WIPという行では、PlansParserは "completed" と判定しますが、getPlansStatus()は "wip" とカウントするため、ユーザーに異なる進捗情報が表示される恐れがあります。commands/core/work.md-516-523 (1)
516-523: 関連ファイル検証セクションのコードフェンスに言語指定を追加してください。Line 516 と Line 525 のフェンスが未指定で MD040 に該当します。
textなどを指定してください。🧩 修正案
-``` +```text Run related files verification: → Detect function signature changes → check callers @@ → Detect config changes → check related configs@@
-+text
📋 Related Files Verification
@@
3. Show LSP find-referencesAlso applies to: 525-536
skills/verify/references/verify-related-files.md-105-113 (1)
105-113: 出力例フェンスの言語指定が不足しています。Line 105 ほか複数のフェンスが MD040 に該当します。
text等の言語指定を全箇所に付与してください。🧩 修正例(他の未指定フェンスにも同様に適用)
-``` +```text 変更された関数/型を特定 ↓ LSP find-references を実行 ↓ 全参照箇所をリスト化 ↓ 未編集の参照箇所を警告</details> Also applies to: 139-146, 170-177, 194-200, 204-206, 213-220, 238-248, 252-274, 278-298 </blockquote></details> <details> <summary>skills/verify/SKILL.md-164-178 (1)</summary><blockquote> `164-178`: **このコードフェンスにも言語指定を付けてください。** Line 164 のフェンスが未指定で MD040 に該当します。`text` などを指定してください。 <details> <summary>🧩 修正案</summary> ```diff -``` +```text 編集ファイルを取得 ↓ ┌─────────────────────────────────────────┐ │ 関連ファイル検証 │ @@ 修正漏れ候補を警告</details> </blockquote></details> <details> <summary>commands/optional/session-inbox.md-89-96 (1)</summary><blockquote> `89-96`: **コードフェンスに言語指定を追加してください。** Line 89 のフェンスが言語未指定で MD040 に該当します。`text` などを付与してください。 <details> <summary>🧩 修正案</summary> ```diff -``` +```text You: このファイルを編集して @@ Claude: 承知しました。UserAPI の変更を考慮して編集します...</details> </blockquote></details> </blockquote></details> <details> <summary>🧹 Nitpick comments (7)</summary><blockquote> <details> <summary>mcp-server/src/tools/session.ts (1)</summary><blockquote> `240-244`: **`toLocaleTimeString()` はロケールに依存し、出力形式が非決定的です。** サーバー環境やユーザーの設定によって時刻形式が変わるため、一貫性のある出力を得るには明示的なフォーマットを指定してください。 <details> <summary>♻️ 修正案</summary> ```diff const formatted = unread .map((msg) => { - const time = new Date(msg.timestamp).toLocaleTimeString(); + const date = new Date(msg.timestamp); + const time = date.toISOString().slice(11, 19); // HH:MM:SS return `[${time}] ${msg.client}: ${msg.message}`; }) .join("\n");mcp-server/src/index.ts (1)
52-62: ルーティングのプレフィックスパターンに一貫性がありません。
harness_session_*とharness_workflow_*はアンダースコア区切りですが、harness_statusには後続のアンダースコアがありません。startsWith("harness_status")はharness_status_verboseのような将来のツールにもマッチしますが、harness_statusXYZのような意図しないツール名にもマッチします。一貫性のため
harness_status_*パターンに統一するか、または完全一致と組み合わせることを検討してください。♻️ より明確なルーティング
- if (name.startsWith("harness_status")) { + if (name === "harness_status" || name.startsWith("harness_status_")) { return await handleStatusTool(name, args); }scripts/pretooluse-inbox-check.sh (2)
18-18: 未使用の変数SESSION_FILEを削除してください
SESSION_FILEは宣言されていますが、スクリプト内で使用されていません。不要なコードは混乱を招くため削除を推奨します。🔧 修正案
SESSIONS_DIR=".claude/sessions" BROADCAST_FILE="${SESSIONS_DIR}/broadcast.md" -SESSION_FILE=".claude/state/session.json" CHECK_INTERVAL_FILE="${SESSIONS_DIR}/.last_inbox_check"
22-28: 未使用の変数INPUTを削除してくださいstdin から JSON を読み取っていますが、
INPUT変数はその後使用されていません。必要がなければ、このコードブロック全体を削除することを検討してください。🔧 修正案
-# ===== stdin から JSON 入力を読み取り ===== -INPUT="" -if [ -t 0 ]; then - : # stdin が TTY の場合は入力なし -else - INPUT=$(cat 2>/dev/null || true) -fi - # ===== チェック間隔の確認 =====scripts/session-list.sh (2)
12-12: 未使用の変数SCRIPT_DIRを削除してください
SCRIPT_DIRは宣言されていますが、スクリプト内で使用されていません。🔧 修正案
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - # ===== 設定 =====
80-83: 未使用の変数と未実装のクリーンアップ処理
active_count、stale_count、tmp_fileが宣言されていますが使用されていません。コメントには「古いセッションをクリーンアップ」と記載されていますが、実装されていません。不要であれば削除するか、TODO コメントを追加してください。🔧 修正案(未使用変数の削除)
- # 古いセッションをクリーンアップしながら表示 - local active_count=0 - local stale_count=0 - local tmp_file=$(mktemp) - echo "| セッションID | 最終アクティブ | 状態 |"scripts/session-inbox-check.sh (1)
14-14: 未使用の変数SCRIPT_DIRを削除してください
SCRIPT_DIRは宣言されていますが、スクリプト内で使用されていません。🔧 修正案
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - # ===== 設定 =====
| function loadSessions(): Record<string, Session> { | ||
| ensureSessionsDir(); | ||
| if (fs.existsSync(ACTIVE_FILE)) { | ||
| try { | ||
| return JSON.parse(fs.readFileSync(ACTIVE_FILE, "utf-8")); | ||
| } catch { | ||
| return {}; | ||
| } | ||
| } | ||
| return {}; | ||
| } | ||
|
|
||
| function saveSessions(sessions: Record<string, Session>): void { | ||
| ensureSessionsDir(); | ||
| fs.writeFileSync(ACTIVE_FILE, JSON.stringify(sessions, null, 2)); | ||
| } |
There was a problem hiding this comment.
ファイル読み書きに競合状態(Race Condition)のリスクがあります。
複数のセッションが同時に loadSessions() → saveSessions() を実行すると、後から書き込んだセッションが先のセッションの変更を上書きする可能性があります。
ファイルロックの導入、または atomic write パターン(一時ファイルに書き込んでからリネーム)の検討を推奨します。
🤖 Prompt for AI Agents
In `@mcp-server/src/tools/session.ts` around lines 103 - 118, The
loadSessions/saveSessions pair has a race where concurrent saveSessions() calls
can overwrite each other; modify saveSessions to perform an atomic write by
writing JSON to a temporary file in the same directory (e.g., ACTIVE_FILE +
".tmp" or using a unique suffix), fsync the temp file (or file descriptor), then
rename/replace the temp to ACTIVE_FILE (fs.renameSync) to ensure atomic replace;
keep loadSessions() unchanged but ensure it reads ACTIVE_FILE only after rename
so it never sees a partially-written file; alternatively, if you prefer locking,
add an exclusive lock around loadSessions/saveSessions (e.g., using a simple
advisory lock or a small lockfile) to serialize access to ACTIVE_FILE.
| cat <<EOF | ||
| {"hookSpecificOutput":{"hookEventName":"AutoBroadcast","additionalContext":"📢 自動ブロードキャスト: ${FILE_NAME} の変更を他セッションに通知しました"}} | ||
| EOF |
There was a problem hiding this comment.
Hook JSON の文字列をエスケープしてください。
FILE_NAME に ", \ などが含まれると JSON が壊れ、フック処理が失敗する可能性があります。jq -n --arg で JSON を生成する形にしてください。
🧩 修正案
- # 通知メッセージを出力
- cat <<EOF
-{"hookSpecificOutput":{"hookEventName":"AutoBroadcast","additionalContext":"📢 自動ブロードキャスト: ${FILE_NAME} の変更を他セッションに通知しました"}}
-EOF
+ # 通知メッセージを出力(JSON エスケープ)
+ msg="📢 自動ブロードキャスト: ${FILE_NAME} の変更を他セッションに通知しました"
+ jq -n --arg msg "$msg" \
+ '{"hookSpecificOutput":{"hookEventName":"AutoBroadcast","additionalContext":$msg}}'📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| cat <<EOF | |
| {"hookSpecificOutput":{"hookEventName":"AutoBroadcast","additionalContext":"📢 自動ブロードキャスト: ${FILE_NAME} の変更を他セッションに通知しました"}} | |
| EOF | |
| # 通知メッセージを出力(JSON エスケープ) | |
| msg="📢 自動ブロードキャスト: ${FILE_NAME} の変更を他セッションに通知しました" | |
| jq -n --arg msg "$msg" \ | |
| '{"hookSpecificOutput":{"hookEventName":"AutoBroadcast","additionalContext":$msg}}' |
🤖 Prompt for AI Agents
In `@scripts/session-auto-broadcast.sh` around lines 96 - 98, The hook JSON
currently interpolates FILE_NAME directly into a here-doc (cat <<EOF ...) which
can break if FILE_NAME contains quotes or backslashes; replace the heredoc JSON
construction with a safe JSON generator using jq -n --arg to pass FILE_NAME
(e.g. use jq -n --arg fileName "$FILE_NAME" '{hookSpecificOutput:
{hookEventName:"AutoBroadcast", additionalContext: ("📢 自動ブロードキャスト: " +
$fileName + " の変更を他セッションに通知しました")}}' ) and emit that output instead of the raw
heredoc so the FILE_NAME is properly escaped.
| # ブロードキャストファイルに追記 | ||
| { | ||
| echo "" | ||
| echo "## ${timestamp} [${short_id}]" | ||
| echo "${prefix}${message}" | ||
| } >> "$BROADCAST_FILE" | ||
|
|
||
| # 古いメッセージを削除(MAX_MESSAGES を超えた場合) | ||
| if [ -f "$BROADCAST_FILE" ]; then | ||
| local msg_count=$(grep -c "^## " "$BROADCAST_FILE" 2>/dev/null || echo "0") | ||
| if [ "$msg_count" -gt "$MAX_MESSAGES" ]; then | ||
| # 最新の MAX_MESSAGES 件のみ保持 | ||
| local temp_file=$(mktemp) | ||
| local skip_count=$((msg_count - MAX_MESSAGES)) | ||
| awk -v skip="$skip_count" ' | ||
| /^## / { count++ } | ||
| count > skip { print } | ||
| ' "$BROADCAST_FILE" > "$temp_file" | ||
| mv "$temp_file" "$BROADCAST_FILE" | ||
| fi |
There was a problem hiding this comment.
broadcast.md の追記・剪定はロックで保護してください。
複数セッションの同時実行で追記や剪定が競合し、メッセージ欠落や破損が起き得ます。ファイルロックで保護してください。
🧩 修正案(flock がある場合のみロック)
+ if command -v flock >/dev/null 2>&1; then
+ exec 200>"${BROADCAST_FILE}.lock"
+ flock -x 200
+ fi
+
# ブロードキャストファイルに追記
{
echo ""
echo "## ${timestamp} [${short_id}]"
echo "${prefix}${message}"
} >> "$BROADCAST_FILE"
@@
if [ -f "$BROADCAST_FILE" ]; then
local msg_count=$(grep -c "^## " "$BROADCAST_FILE" 2>/dev/null || echo "0")
if [ "$msg_count" -gt "$MAX_MESSAGES" ]; then
@@
mv "$temp_file" "$BROADCAST_FILE"
fi
fi
+
+ if command -v flock >/dev/null 2>&1; then
+ flock -u 200
+ exec 200>&-
+ fi🧰 Tools
🪛 Shellcheck (0.11.0)
[warning] 99-99: Declare and assign separately to avoid masking return values.
(SC2155)
[warning] 102-102: Declare and assign separately to avoid masking return values.
(SC2155)
🤖 Prompt for AI Agents
In `@scripts/session-broadcast.sh` around lines 90 - 109, Wrap the append and
pruning sequence that writes to BROADCAST_FILE (the echo "## ${timestamp} ..."
block, the msg_count grep, mktemp creation, awk filtering and mv) inside a file
lock; detect and prefer flock when available (e.g. if command -v flock) and
otherwise use an exclusive lock via a dedicated lock fd (exec
200>"${BROADCAST_FILE}.lock"; flock 200 ...; exec 200>&-). Ensure mktemp and
temp_file creation/cleanup and the mv into BROADCAST_FILE happen while the lock
is held, and release the lock after completion; add a trap to remove temp_file
on exit/error to avoid stale temp files. This will protect BROADCAST_FILE and
the MAX_MESSAGES pruning logic from concurrent runs.
| if [ "${HOOK_OUTPUT:-}" = "true" ]; then | ||
| cat <<EOF | ||
| {"hookSpecificOutput":{"hookEventName":"Broadcast","additionalContext":"📤 ブロードキャスト送信: ${message:0:50}..."}} | ||
| EOF |
There was a problem hiding this comment.
Hook 用 JSON もエスケープ処理が必要です。
message に " や \ が含まれると JSON が壊れます。jq -n --arg などで生成してください。
🧩 修正案
# JSON 出力(hooks 用)
if [ "${HOOK_OUTPUT:-}" = "true" ]; then
- cat <<EOF
-{"hookSpecificOutput":{"hookEventName":"Broadcast","additionalContext":"📤 ブロードキャスト送信: ${message:0:50}..."}}
-EOF
+ if command -v jq >/dev/null 2>&1; then
+ jq -n --arg msg "📤 ブロードキャスト送信: ${message:0:50}..." \
+ '{"hookSpecificOutput":{"hookEventName":"Broadcast","additionalContext":$msg}}'
+ else
+ local escaped
+ escaped=$(printf '%s' "📤 ブロードキャスト送信: ${message:0:50}..." | sed 's/\\/\\\\/g; s/"/\\"/g')
+ echo "{\"hookSpecificOutput\":{\"hookEventName\":\"Broadcast\",\"additionalContext\":\"$escaped\"}}"
+ fi
fi📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if [ "${HOOK_OUTPUT:-}" = "true" ]; then | |
| cat <<EOF | |
| {"hookSpecificOutput":{"hookEventName":"Broadcast","additionalContext":"📤 ブロードキャスト送信: ${message:0:50}..."}} | |
| EOF | |
| if [ "${HOOK_OUTPUT:-}" = "true" ]; then | |
| if command -v jq >/dev/null 2>&1; then | |
| jq -n --arg msg "📤 ブロードキャスト送信: ${message:0:50}..." \ | |
| '{"hookSpecificOutput":{"hookEventName":"Broadcast","additionalContext":$msg}}' | |
| else | |
| local escaped | |
| escaped=$(printf '%s' "📤 ブロードキャスト送信: ${message:0:50}..." | sed 's/\\/\\\\/g; s/"/\\"/g') | |
| echo "{\"hookSpecificOutput\":{\"hookEventName\":\"Broadcast\",\"additionalContext\":\"$escaped\"}}" | |
| fi | |
| fi |
🤖 Prompt for AI Agents
In `@scripts/session-broadcast.sh` around lines 116 - 119, The current here-doc
emits unescaped JSON using the raw ${message:0:50}, breaking when message
contains " or \; replace the heredoc block that prints hookSpecificOutput with a
jq-based generator (use jq -n --arg truncated "$(echo "${message}" | cut
-c1-50)" '{hookSpecificOutput: {hookEventName:"Broadcast", additionalContext:
("📤 ブロードキャスト送信: " + $truncated)}}') so jq handles proper JSON escaping; update
the branch that checks HOOK_OUTPUT to call jq (or fail/emit a safe fallback if
jq is unavailable) and reference the HOOK_OUTPUT check and the message variable
in the change.
Changes based on code review findings: 1. **Code Quality (P0)**: - Extract getProjectRoot() to shared utils.ts module - Change package name from @Anthropic-AI to @claude-code-harness - Add safeReadJSON/safeWriteJSON with error logging 2. **Performance**: - Convert execSync to async exec in getRecentChangesAsync() - Use shared constants for thresholds and limits 3. **Maintainability**: - Replace magic numbers with named constants - Add type definitions for session/message data - Extract plan template generation to separate function 4. **Shell Scripts**: - Add trap cleanup for temp files in session-broadcast.sh - Add trap cleanup for temp files in session-list.sh - Remove unused dead code (unused mktemp) Co-Authored-By: Claude <noreply@anthropic.com>
Implemented features learned from Clawdbot competitor analysis:
Session Communication (Phase 1):
Hooks Integration:
External Automation (Phase 2):
Future Design (Phase 4):
This maintains Harness's lightweight plugin philosophy while
adding real-time-ish session awareness capabilities.
https://claude.ai/code/session_01GgwkKJXFZpZWdh1etQbvaw
Summary by CodeRabbit
新機能
ドキュメント
変更
✏️ Tip: You can customize this high-level summary in your review settings.