Skip to content

actas-claim: bare トークンの生存確認が構造的に常に false を返し、誰でも claim を奪える #33

Description

@orangewk

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_alivelib/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 出力は完全同文で、heldunknown も出ていない。

気づく手段が無い

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 ロックに履歴が残らないため特定不能)

考えられる手当て(提案。設計はそちらの判断で)

  1. bare トークンの生存確認を「常に false」以外にする。 該当ファイルが存在しないことと、プロセスが死んでいることは別。判定不能なら stale ではなく「判定不能」を返し、呼び出し側に判断させる
  2. exit code で bare 成立を区別できるようにする。 現状は composite での成立と bare での成立が同じ exit 0。呼び出し側が stderr を読まないと劣化に気づけない
  3. session-start.sh を経由しないホストでも cc-instance.<pid> が書かれるようにするactas-claim.sh 側で書く等)

再現手順(記載のみ・未実行)

本番の ~/.agents/skills/agmsg には触れず、隔離コピーで行うこと。

  1. 検証用 SKILL_DIR に team を作り、agent=testrole, type=claude-code, project=<検証パス> を登録
  2. run/cc-instance.* を一切作らない
  3. 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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions