actas-claim.sh の役割排他が、ある条件下で誰の claim でも無条件に奪える状態になる。競合状態ではなく決定論的に毎回起きる。
2026-07-30 に galaxias の運用で実際に踏んだ(2 つのセッションが両方とも同じ役割を名乗り、両方とも claimed を取得した)。観察記録は orangewk/galaxias の docs/findings/2026-07-30-xipress-role-exclusion-not-excluding.md(PR orangewk/galaxias#566 で merged)。
事象
lib/actas-lock.sh の _actas_lock_try_claim は、既存 owner の生存確認に失敗すると stale として奪取を許す。
if [ -z "$existing" ] || ! actas_lock_sid_alive "$existing"; then
echo "stale"
生存確認 agmsg_instance_alive(lib/instance-id.sh)は、トークンが bare(<sid> のみ、.pid サフィックス無し)の場合、run/cc-instance.<pid> を全走査して内容一致するものが 1 件でもあるかでしか判定しない。該当ファイルが存在しなければ無条件に「生存していない」を返す。
run/cc-instance.<pid> を書くのは session-start.sh だけである。
したがって、session-start.sh を経由しないホスト構成では cc-instance.<pid> が一度も書かれず、bare トークンの生存確認が構造的に常に false を返す。
結果として、owner が bare トークンになった時点で、以降どのセッションが claim を試みても stale と誤判定され、無条件に奪取が成立する。元のセッションが実際に生きているかは一切見られない。
実測(2026-07-30、galaxias 環境)
| 項目 |
値 |
run/actas.galaxias__xipress.session |
ab9e04b5-...(bare。.pid 無し) |
run/actas.mathdesk-desktop__xipress.session |
同一値 |
run/cc-instance.* |
0 件(run/ 全体を走査) |
| この環境の claude-code 種の actas ロック |
3 件とも例外なく bare。composite は 1 件も無い |
セッション A(03:01 開始、claim 取得)が稼働中のまま、セッション B(15:15 開始)も claimed を取得した。両者の SessionStart 出力は完全同文で、held も unknown も出ていない。
気づく手段が無い
agmsg_instance_id()(lib/instance-id.sh)は agent-pid 解決に失敗して bare へ落ちるとき、stderr に診断を出す設計になっている。
agmsg: instance-id falling back to bare session_id (agent pid unresolved for type=%s); parallel --continue/--resume isolation is degraded
この診断自体は正しく出ている。 問題は、actas-claim.sh が bare での成立も exit 0(成功)で返すため、呼び出し側が「claimed したが排他は効いていない」を区別できないこと。呼び出し側が stderr を読まなければ、劣化は完全に無音になる。
未確認
- なぜ
agmsg_agent_pid()(祖先プロセスウォーク)が失敗するのか。 WSL bash に解決される罠は該当しないことを確認済み(呼び出し側が Git Bash を明示的に選び、実機にも存在する)。MSYS の ps がネイティブ Windows プロセスの PPID を辿れない可能性が候補だが、実行を禁止した調査だったため未検証
- ロックの現在の owner が誰か(actas ロックに履歴が残らないため特定不能)
考えられる手当て(提案。設計はそちらの判断で)
- bare トークンの生存確認を「常に false」以外にする。 該当ファイルが存在しないことと、プロセスが死んでいることは別。判定不能なら
stale ではなく「判定不能」を返し、呼び出し側に判断させる
exit code で bare 成立を区別できるようにする。 現状は composite での成立と bare での成立が同じ exit 0。呼び出し側が stderr を読まないと劣化に気づけない
session-start.sh を経由しないホストでも cc-instance.<pid> が書かれるようにする(actas-claim.sh 側で書く等)
再現手順(記載のみ・未実行)
本番の ~/.agents/skills/agmsg には触れず、隔離コピーで行うこと。
- 検証用
SKILL_DIR に team を作り、agent=testrole, type=claude-code, project=<検証パス> を登録
run/cc-instance.* を一切作らない
AGMSG_AGENT_PID=(空文字。instance-id.sh がテスト用に文書化している escape hatch)で agent-pid 解決を強制失敗させて呼ぶ
AGMSG_AGENT_PID= bash actas-claim.sh <project> claude-code testrole SESSION_A
AGMSG_AGENT_PID= bash actas-claim.sh <project> claude-code testrole SESSION_B
SESSION_A を終了させていないのに SESSION_B の claim が status=ok になり、ロックが上書きされることを確認する。
実害について
2026-07-30 の観測範囲では実害は確認されていない。二重に立った見張りの片方は agmsg の消費がゼロで、取りこぼしは無かった。ただし運用で回避できただけで、機構としては 2 セッションが同時に同じ役割を名乗れる状態が続いている。
関連
- 呼び出し側(galaxias)の対応は orangewk/galaxias 側に別途起票する。そちらは「呼び出し側が劣化を検出できない」件で、本 issue(生存確認そのものの構造)とは別
- 調査は独立の調査役が read-only で実施(ファイル変更・状態変更系スクリプトの実行を禁止)
報告: Xipress (61b26ce6)
actas-claim.shの役割排他が、ある条件下で誰の claim でも無条件に奪える状態になる。競合状態ではなく決定論的に毎回起きる。2026-07-30 に galaxias の運用で実際に踏んだ(2 つのセッションが両方とも同じ役割を名乗り、両方とも
claimedを取得した)。観察記録は orangewk/galaxias のdocs/findings/2026-07-30-xipress-role-exclusion-not-excluding.md(PR orangewk/galaxias#566 で merged)。事象
lib/actas-lock.shの_actas_lock_try_claimは、既存 owner の生存確認に失敗するとstaleとして奪取を許す。生存確認
agmsg_instance_alive(lib/instance-id.sh)は、トークンが bare(<sid>のみ、.pidサフィックス無し)の場合、run/cc-instance.<pid>を全走査して内容一致するものが 1 件でもあるかでしか判定しない。該当ファイルが存在しなければ無条件に「生存していない」を返す。run/cc-instance.<pid>を書くのはsession-start.shだけである。したがって、
session-start.shを経由しないホスト構成ではcc-instance.<pid>が一度も書かれず、bare トークンの生存確認が構造的に常に false を返す。結果として、owner が bare トークンになった時点で、以降どのセッションが claim を試みても
staleと誤判定され、無条件に奪取が成立する。元のセッションが実際に生きているかは一切見られない。実測(2026-07-30、galaxias 環境)
run/actas.galaxias__xipress.sessionab9e04b5-...(bare。.pid無し)run/actas.mathdesk-desktop__xipress.sessionrun/cc-instance.*run/全体を走査)セッション A(03:01 開始、claim 取得)が稼働中のまま、セッション B(15:15 開始)も
claimedを取得した。両者の SessionStart 出力は完全同文で、heldもunknownも出ていない。気づく手段が無い
agmsg_instance_id()(lib/instance-id.sh)は agent-pid 解決に失敗して bare へ落ちるとき、stderr に診断を出す設計になっている。この診断自体は正しく出ている。 問題は、
actas-claim.shが bare での成立もexit 0(成功)で返すため、呼び出し側が「claimed したが排他は効いていない」を区別できないこと。呼び出し側が stderr を読まなければ、劣化は完全に無音になる。未確認
agmsg_agent_pid()(祖先プロセスウォーク)が失敗するのか。 WSL bash に解決される罠は該当しないことを確認済み(呼び出し側が Git Bash を明示的に選び、実機にも存在する)。MSYS のpsがネイティブ Windows プロセスの PPID を辿れない可能性が候補だが、実行を禁止した調査だったため未検証考えられる手当て(提案。設計はそちらの判断で)
staleではなく「判定不能」を返し、呼び出し側に判断させるexit codeで bare 成立を区別できるようにする。 現状は composite での成立と bare での成立が同じexit 0。呼び出し側が stderr を読まないと劣化に気づけないsession-start.shを経由しないホストでもcc-instance.<pid>が書かれるようにする(actas-claim.sh側で書く等)再現手順(記載のみ・未実行)
本番の
~/.agents/skills/agmsgには触れず、隔離コピーで行うこと。SKILL_DIRに team を作り、agent=testrole, type=claude-code, project=<検証パス>を登録run/cc-instance.*を一切作らないAGMSG_AGENT_PID=(空文字。instance-id.shがテスト用に文書化している escape hatch)で agent-pid 解決を強制失敗させて呼ぶSESSION_A を終了させていないのに SESSION_B の claim が
status=okになり、ロックが上書きされることを確認する。実害について
2026-07-30 の観測範囲では実害は確認されていない。二重に立った見張りの片方は agmsg の消費がゼロで、取りこぼしは無かった。ただし運用で回避できただけで、機構としては 2 セッションが同時に同じ役割を名乗れる状態が続いている。
関連
報告: Xipress (61b26ce6)