Claude Code を何本も起こして仕事を分担させられるかを技術検証したときに、ROCm で生成モデルを回す仕事もエージェントに任せました。ROCm では環境変数を1つ間違えるだけで静かに壊れますが、正しい設定を手順書やプロンプトに書いても、エージェントには守られませんでした。

結論

  • 守らせたい環境変数は、プロンプトではなくコードに畳みます。生成は薄いシェルの入口(ラッパースクリプト)からだけ叩く約束にし、変数を消す・立てる・venv を選ぶ、を入口が受け持ちます。
  • 入口は readlink -f "$0" で自分の場所を決めます。symlink 越しに呼ばれた入口が場所を間違えて落ち、エージェントが入口を諦めて python を直に叩き始めたことがありました。入口が落ちれば、防御も一緒に外れます。
  • 入口に、使う venv を環境変数で選べる口を付けておくと、新しい venv を切り替える前に同じ入口で試せます。

試した環境

  • GPU: AMD Radeon RX 7900 XT(gfx1100、VRAM 20 GiB)
  • OS と ROCm: Ubuntu、ROCm 7.2.4
  • ライブラリ: torch 2.13.0+rocm7.2
  • 入口は /bin/sh のスクリプト

プロンプトに書いても守られない

1つ間違えるだけで壊れる環境変数は、次のような物でした。どれもエラーで止まらず、「VRAM が足りない」「遅いだけ」に見えます。

入口がすること防いでいること
PYTORCH_HIP_ALLOC_CONF を消すexpandable_segments:True だと、21 GB 空いていても 256 MB の確保で OOM になる
効果音のときだけ TORCH_COMPILE_DISABLE=1 TORCHDYNAMO_DISABLE=1 を立てる立て忘れると 70 秒/step、GPU 使用率 0% のまま(約 270 倍遅い)
音楽は別の venv の python で起こすtransformers の版が噛み合わない(ACE-Step 1.5 は <4.58)

これらを手順書やプロンプトに書いても、エージェントには守られませんでした。そこで、生成は入口だけから叩く約束にし、入口の中で毎回そろえるようにしました。

入口では HF_HOME も既定の値で立てるようにしました。非対話の ssh や systemd では空になり、~/.cache/huggingface に重みを落とし直してしまうためです。

入口に畳む

入口の見出しと、防御の行の要所です(名前と場所は例に置き換えています)。

#!/bin/sh
# 生成の入口。エージェントはこれだけを叩く。
set -eu

# symlink を解決してから自分の場所を決める
SELF=$(readlink -f "$0" 2>/dev/null || echo "$0")
HERE=$(cd "$(dirname "$SELF")" && pwd)

VENV="${GEN_VENV:-/path/to/models/venv}"              # 画像・効果音・検査
VENV_ACE="${GEN_VENV_ACE:-/path/to/models/venv-ace}"  # 音楽(transformers<4.58)
export HF_HOME="${HF_HOME:-/path/to/models/hf}"

# 設定しないこと。確保そのものを壊す
unset PYTORCH_HIP_ALLOC_CONF || true

# …(コマンドの振り分け)…
[ "$cmd" = sfx ] && { export TORCH_COMPILE_DISABLE=1 TORCHDYNAMO_DISABLE=1; }
# 音楽は VENV_ACE の python で起こし、HSA_OVERRIDE_GFX_VERSION=11.0.0 を付ける

入口を通れば、表に挙げた3つは毎回そろいます。ただし HF_HOME と使う venv は、外から渡した値があればそちらを優先します(venv を選べるのは、後で述べる、新しい venv を試すための口です)。人が手で打つときも同じ入口を使いました。

入口は、どこからでも呼べるように /usr/local/bin に symlink を置いていました。このとき $0 は symlink 自身なので、素直に dirname "$0" すると /usr/local/bin の下に本体のスクリプトを探しに行き、見つからずに落ちます。次の図は、そのとき起きたことです。

symlink 越しに呼ばれて防御が外れるまで

エージェントは入口が落ちたのを見て、入口を諦め、python を直に叩き始めました。入口の中で消していた変数も、立てていた変数も、そこで効かなくなります。防御を道具に畳んでも、道具そのものが呼ばれ方で落ちれば、迂回されて終わりでした。

そこで、readlink -f "$0" で symlink を解決してから、自分の場所を決めるようにしました。readlink -f が使えない環境のために、失敗したら $0 をそのまま使うようにしてあります。

向きを間違えた道しるべの門の前で、小さなロボットが柵の脇道を回り込んでいく絵

新しい venv を試すときにも使える

入口は、使う venv を環境変数(上の例では GEN_VENV・GEN_VENV_ACE)で選べます。新しく作った venv を、それまでの venv の場所に切り替える前に、この変数だけを新しい方へ向けて、過去のジョブと同じ引数で、生成の種類ごとに1回ずつ通せます。防御の行を通ったまま試せるので、試したときと使うときで条件がずれません。

おわりに

エージェントに守らせたい設定は、文章で頼むより、必ず通る入口のコードに書いてください。そして、その入口が symlink 越しでも落ちないことまで確かめておくと、防御が迂回されずに済みます。

※ Claude Code の振る舞いと、ROCm・PyTorch の挙動は執筆時点の物で、いまは変わっている可能性があります。