会话环境
运行时进程启动时拿到什么:环境变量、PATH、临时目录与登录凭据;以及它怎么结束:进程组、信号与超时。目标是调用方能完整控制会话环境,同时调用方环境里残留的变量不会悄悄改变登录方式。
代码位置
| 文件 | 主要类型与函数 | 本页对应的章节 |
|---|---|---|
src/session.rs | Session(assemble、runtime_dir)、home_placeholder、CODEX_AUTH_VARIABLE、PI_AUTH_VARIABLE、SessionInputs(read、assemble)、find_executable、is_executable | 环境变量、--env、env 文件格式、PATH |
src/run/mod.rs | create_tempdir | 会话临时目录 |
src/run/execute.rs | execute、temp_environment | 会话临时目录、进程、信号与超时 |
src/runtime/pi.rs、src/runtime/codex.rs、src/sandbox/mod.rs | pi.rs 的 user_state_dir、STATE_HOME_SUBDIR、LOGIN_FILE;sandbox/mod.rs 的 PiState;codex.rs 的 user_home、HOME_SUBDIR、LOGIN_FILE、login_file、check_login、create_home | pi 的状态目录与 codex、dsh 的私有配置目录、多个运行同时进行 |
src/runtime/dsh.rs | REMOVED_PREFIX、KEY_VARIABLE | 环境变量、dsh 的私有配置目录、凭据 |
src/credential.rs | write_session_credential | 凭据 |
src/process_tree/ | mod.rs 的 ProcessTable、Recorded、wait_for_exit、kill_group_members、kill_tree、wrapped_child;linux.rs 与 macos.rs 的 claim_orphans、ForkWatcher、snapshot | 进程、信号与超时 |
src/run/signal.rs | Signals(install、prepare、checking、doctoring、running、finishing、exited、kill_group、stop_for_limit、hold) | 进程、信号与超时 |
Signals 在进程启动时安装一次,之后按运行所处的阶段决定收到信号时的动作:启动前只记录,检查沙箱时终止检查进程,运行中转发给进程组。理由:信号可能在任何时刻到达,每个阶段的清理动作不同,由同一个对象记录阶段,处理函数里只读它。
环境变量
会话的环境变量按以下顺序组装,后一步覆盖前一步:
- 从调用方自己的环境开始。
- 按运行时删除会改变登录方式的变量,见下表。
- 按给出的顺序加载
--env-file。 - 按给出的顺序应用
--env。 - 读取
AGENTRUN_开头的变量(AGENTRUN_SANDBOX与两个凭据变量),然后从会话环境删除所有AGENTRUN_变量。 - 设置 PATH、
TMPDIR、TMP、TEMP;codex 的CODEX_HOME指向私有配置目录;网络经过滤代理时设置代理变量;claude-code 设置CLAUDE_CODE_DISABLE_AUTO_MEMORY=1,理由见运行;claude-code 的沙箱运行时设置SOCAT_DEFAULT_LISTEN_IP=4,理由见网络;dsh 设置DSH_HOME、DSH_AGENTS_HOME、DSH_PERMISSION_MODE与DSH_TELEMETRY_DISABLED,见dsh 的私有配置目录。
| 运行时 | 删除的变量 | 效果 |
|---|---|---|
| claude-code | 以 ANTHROPIC_ 或 CLAUDE_CODE_USE_ 开头的变量 | 使用订阅登录,不受调用方环境里的 API key 或第三方端点影响 |
| codex | CODEX_API_KEY、OPENAI_API_KEY、OPENAI_BASE_URL | 使用 auth.json 里的登录 |
| pi | 不删除 | pi 从环境变量或 auth.json 读取服务商 key |
| dsh | 以 DSH_ 开头的变量 | dsh 只用 agentrun 给出的私有目录与设置,调用方环境里的 DSH_TOOLS_MODE、DSH_TELEMETRY_OTLP_URL 这类变量不改变工具与上报。DEEPSEEK_API_KEY 与 DEEPSEEK_BASE_URL 不删除 |
表里没有的变量原样进入会话。claude-code 读取的其他 CLAUDE_ 变量(例如 Cloud Managed 容器里设置的 CLAUDE_CODE_INCLUDE_PARTIAL_MESSAGES,它让 claude-code 多输出 stream_event,这些事件只写入 --raw)不删除,因为 CLAUDE_CODE_OAUTH_TOKEN 这样的登录变量也在这个前缀之下。
删除只作用于从调用方环境继承来的变量,--env 与 --env-file 显式给出的变量不受影响。理由:删除规则防的是调用方没有意识到的残留变量;显式给出的变量是调用方的意图,例如用 --env ANTHROPIC_BASE_URL=... 接入兼容 Anthropic 接口的其他服务。这时登录方式由调用方负责,和 -- 透传一样。
AGENTRUN_ 变量从会话环境删除的理由:凭据变量里是登录文件的完整内容,留在会话环境里,agent 运行 env 就能读到。AGENTRUN_ 变量可以来自 --env 与 --env-file,所以在第 5 步才读取。
--env
KEY=VALUE:第一个等号之后的全部内容都是值。- 只写
KEY:取第 2 步删除之前调用方环境里的值。调用方环境里没有这个变量时启动前报错(退出码 2),理由是静默忽略会让调用方以为变量已经传进去了。 - KEY 必须匹配
[A-Za-z_][A-Za-z0-9_]*,否则是用法错误。
env 文件格式
# 注释行与空行忽略
AGENTRUN_SANDBOX=off
export CLAUDE_CODE_OAUTH_TOKEN="..."
GREETING='hello world'
每行 KEY=VALUE,行尾的 \r 去掉;允许 export 前缀;等号两侧不允许空格;值两端成对的单引号或双引号去掉,引号内不处理转义;不做变量展开,不支持多行值;未加引号的值里的 # 是值的一部分。文件不存在、不是 UTF-8 或某一行格式错误时,启动前报错(退出码 2),错误信息写出文件名与行号,不写出这一行的内容。
工具不自动加载工作目录里的 .env。理由:工作目录 agent 可写,agent 可以写一个 .env 改变下一次运行的登录方式或 PATH;项目里的 .env 也常有调用方不打算交给 agent 的密钥。
只显示变量名
start 事件的 env 字段是 --env-file 与 --env 设置过的变量名,按首次出现顺序去重,不含 AGENTRUN_ 变量。--debug 与 --dry-run 另外写出被删除的变量名、被设置的变量名、读到的 AGENTRUN_ 变量名与会话 PATH。任何输出都不写变量的值,PATH 除外。
PATH
会话的 PATH 是 --path 给出的目录(转为绝对路径,按给出的顺序)加上组装后的 PATH。运行时(claude、codex、pi、dsh)、bwrap 与 socat 都在这个 PATH 里查找,用找到的绝对路径启动,不解析符号链接。
这样运行时在调用方的终端里能启动,在会话里也能启动。运行时可能是一个 sh 脚本,里面再调用 node 或其他程序,工具无法事先知道它会用到哪些命令。
PATH 不限制 agent 能执行哪些程序,agent 可以用绝对路径执行任何程序。限制 agent 的是沙箱的写范围与网络限制。
会话临时目录
工具在 $TMPDIR(未设或为空时为 /tmp)下创建 agentrun-<随机后缀>,权限 0700。会话的 TMPDIR、TMP、TEMP 指向它,沙箱把它加入可写路径。正常结束、失败、超时与被中断时都删除它;给了 --debug 时保留,路径写到标准错误。claude-code 的沙箱把命令看到的 TMPDIR 改成名为 claude-<uid> 的目录:Linux 上它在会话临时目录之下(云端与本机 Linux 实测);macOS 上它是 /tmp/claude-<uid>,由同一用户的所有 claude 会话共用(macOS 实测)。macOS 上的这个差异被接受,agentrun 不为此限制会话临时目录的位置。
理由:工作目录里不出现临时文件;只开放这一个目录,不把整个 /tmp 设为可写,因为 /tmp 由整台机器共用,里面有其他程序的文件与 socket。
agentrun 自己的文件也放在这里:pi 与 dsh 包裹用的 Seatbelt 配置 seatbelt.sb、过滤代理的 Unix socket、--debug 下的 raw.jsonl、给出 --session-dir 时 pi 的会话记录 pi-session/(运行结束后移到 --session-dir,见保存与继续会话),以及 dsh 的补丁文件 dsh-patch.yml 与私有目录 dsh-home/、dsh-agents/。
pi 的状态目录与 codex、dsh 的私有配置目录
pi 使用用户的状态目录
pi 直接使用用户的状态目录 $PI_CODING_AGENT_DIR,未设时为 ~/.pi/agent。agentrun 不改这个变量,也不另建目录;pi 读到用户完整的 settings.json、models.json、bin/ 与 keybindings.json。agentrun 按同一规则算出这个目录(user_state_dir),用于沙箱放行、读默认模型设置与写入 AGENTRUN_PI_AUTH。目录不存在时在启动前递归创建,新建的每一级权限 0700;创建失败按启动前检查报错,detail 为 cannot create the pi state directory <路径>: <系统错误>。--dry-run 不创建目录。PI_CODING_AGENT_DIR 与 HOME 都没有时算不出目录,不创建也不放行。
沙箱只放行目录里的五个条目可写:auth.json、auth.json.lock、models-store.json、models-store.json.lock、settings.json.lock,即 src/runtime/pi.rs 的 WRITABLE_STATE_ENTRIES。state 读一次目录,生成 PiState:dir 是目录的真实路径;writable 是 dir 下这五个条目的路径,顺序同上,供 macOS 的 Seatbelt 配置逐个放行;login_target 是 auth.json 的真实路径,只在它是指向别处的符号链接时有值,这时链接目标也放行;readonly 是其余条目里跟随符号链接后是目录或普通文件的那些,按文件名排序,供 Linux 的沙箱只读覆盖。各平台怎样放行见沙箱与网络。
理由:pi 刷新 OAuth 令牌时在 <状态目录>/auth.json.lock 上加锁,加锁不跟随符号链接,所以锁必须建在用户的状态目录里,几次运行之间、运行与用户自己的 pi 之间才互斥。pi 启动时就在状态目录里创建 auth.json(内容为 {})与 models-store.json,读 models-store.json 时建 models-store.json.lock,读 settings.json 时建 settings.json.lock(pi 1.0.4)。锁目录建不出来时 pi 不报错停止:读设置文件的锁建不出来时,它只在标准错误警告 Invalid settings file,把设置文件当作不存在,默认模型随之改变。所以这五个条目都要放行;用户没有 auth.json 时,运行后目录里会多出 pi 创建的这两个文件。
codex 的私有配置目录
codex 不直接使用用户的 CODEX_HOME。每次运行由 codex 适配器的 launch 在会话临时目录与过滤代理之后新建一个私有目录(0700),通过 Launch.private_dirs 交给运行框架;运行框架在同一个 Hold 内登记它。--dry-run 不创建。它与会话临时目录在同样的时机删除(正常结束、失败、超时、被中断,以及启动前收到信号),给了 --debug 时同样保留,并在标准错误写一行 [debug] codex home: <路径>。用户的登录文件以符号链接放进去,其他内容不带进来。
| 位置 | 内容 |
|---|---|
与会话临时目录同级的 agentrun-<同一后缀>-codex/,CODEX_HOME 指向它 | auth.json:指向用户登录文件($CODEX_HOME/auth.json,未设时为 ~/.codex/auth.json)的符号链接。继续会话时另有 sessions/ 下复制进来的会话记录,见保存与继续会话。codex 运行中在这里写它自己的数据库、缓存与会话记录 |
用户登录文件的位置按应用 --env-file 与 --env 之后的会话环境计算。调用方给出的 CODEX_HOME 只决定从哪里带入登录文件,组装环境的最后一步把这个变量改为指向私有目录。
几个容易被改坏的决定:
- codex 不用用户的
CODEX_HOME:只加--ignore-user-config与--ignore-rules时,codex 仍读取CODEX_HOME里的AGENTS.md、.env与技能目录(codex 0.159.2 实测与源码)。 - 登录文件用符号链接:codex 刷新 OAuth 令牌时原地改写登录文件(codex 0.159.2 截断后重写,源码),新令牌经链接写回用户的文件。复制一份会让刷新后的令牌只留在私有目录里,服务商轮换刷新令牌后,用户原来的登录就失效了。codex 将来改为先写临时文件再重命名时,链接会被换成普通文件,这个做法随之失效。
- codex 的目录不放在会话临时目录里:
CODEX_HOME在会话的TMPDIR之下时,codex 不创建它执行apply_patch等命令所需的别名(codex 0.159.2 源码)。
几个运行(包括用户自己在沙箱外运行的 pi 或 codex)同时刷新同一个登录文件里的令牌时会发生什么,见多个运行同时进行。
dsh 的私有配置目录
dsh 不使用用户的 ~/.dsh。每次运行由 dsh 适配器的 launch 在会话临时目录里创建 dsh-home/ 与空目录 dsh-agents/(0700),DSH_HOME 与 DSH_AGENTS_HOME 指向它们;--dry-run 不创建。dsh 运行时在 dsh-home/ 里写入 profile 模板、匿名的用户 id 与会话记录(实测约 72 KB),这些内容随会话临时目录删除,给了 --debug 时一起保留。继续会话时 dsh-home/sessions/ 下另有复制进来的会话记录,见保存与继续会话。另外两个变量 DSH_PERMISSION_MODE=danger-full-access 与 DSH_TELEMETRY_DISABLED=1 的理由见运行页的 dsh 一节。这四个变量在 --env 之后设置。
用户 ~/.dsh 里的 .env、AGENTS.md、profile 目录里的插件与补丁、~/.agents 里的指令与技能都读不到。私有目录与补丁文件一起使用,理由:只用补丁时,用户自己的 profile 补丁与 home 补丁能新增插件条目,按 id 关闭的补丁关不掉 agentrun 不知道 id 的条目,profile 目录里也可能装了插件包(dsh 0.2.1-rc.2 源码与实测)。私有目录不增加启动时间,首次使用与重复使用同一目录的耗时相同(实测)。
凭据
本机沿用用户已有的登录,工具不改动用户自己的登录文件。容器里由启动方把登录文件的内容放进环境变量,工具在运行与 doctor 开始时写到运行时读取的位置。这两个变量可以来自调用方环境,也可以来自 --env 与 --env-file。
| 环境变量 | 写入位置 |
|---|---|
AGENTRUN_CODEX_AUTH | $CODEX_HOME/auth.json,未设 CODEX_HOME 时为 ~/.codex/auth.json |
AGENTRUN_PI_AUTH | $PI_CODING_AGENT_DIR/auth.json,未设时为 ~/.pi/agent/auth.json |
一次运行只写当前运行时的变量。变量未设或为空时不写。路径变量与 HOME 从组装后的会话环境读取,相对路径按 --cwd 解析。
写入位置由 codex::user_home 与 pi::user_state_dir 算出,与运行时实际读取的目录是同一个函数。理由:两处分开写时,改了一处的目录约定,凭据就会写到运行时读不到的地方。
每次写入时,工具在同一目录记一个旁注文件 auth.json.agentrun-sha256,内容是这次变量值的 SHA-256(小写十六进制加换行)。下次运行时:登录文件不存在就写入;变量值的 SHA-256 与旁注相同就不动;不同或没有旁注就覆盖登录文件并更新旁注。理由:运行时会在使用中刷新令牌并改写登录文件,变量没变时保留刷新后的令牌;启动方轮换了凭据时,即使登录文件在持久卷上,会话也会改用新账号。
写入前先创建登录文件所在的目录(新建的每一级权限 0700,已有目录不改权限),再对这个目录加排他的 flock,比较旁注、写登录文件、写旁注都在锁内完成。理由:两次运行同时写入不同的值时,没有锁会出现登录文件来自一次、旁注来自另一次,之后的运行按旁注判断会保留错误的账号。登录文件与旁注都用同目录的临时文件写入,以排他方式创建、权限 0600、fsync 后重命名,同时启动的其他运行不会读到写了一半的文件。写入失败时启动前报错(退出码 2),detail 写变量名与路径,不写值。
登录文件是符号链接且链接目标存在时,工具写到链接目标,链接本身保留;目录锁、旁注与临时文件都放在链接目标所在的目录,旁注名是目标文件名加 .agentrun-sha256。链接目标不存在时按普通文件处理,链接被替换为登录文件。理由:几个状态目录链接到同一个登录文件时,它们的写入因此排队,旁注也只有一份。--debug 的 credentials: 一行与错误信息里的路径仍是链接本身的路径。
claude-code 在容器里用它自己的令牌变量 CLAUDE_CODE_OAUTH_TOKEN 登录,没有登录文件。工具不做转换,只保证它不被删除(它不以 ANTHROPIC_ 或 CLAUDE_CODE_USE_ 开头)。
dsh 用 DeepSeek 的 API key 登录,没有登录文件,也没有对应的凭据变量:key 由调用方放进会话环境的 DEEPSEEK_API_KEY,来自调用方环境、--env 或 --env-file 都可以。会话环境里没有这个变量或值为空时启动前报错(退出码 2),见运行页的 dsh 一节。
codex 要求登录信息以文件形式保存,登录文件不存在时启动前报错,提示先运行 codex login 或在容器里用 AGENTRUN_CODEX_AUTH 传入。
凭据内容不出现在事件流、标准错误、--raw 与错误信息里,只显示变量名与文件路径。
进程、信号与超时
无论运行以哪种方式结束,都满足三条:事件流以 end 结尾;会话临时目录被删除(--debug 除外);运行时及其子进程不再运行。
- 运行时启动在新的进程组里,终端的 Ctrl-C 只发给 agentrun,由 agentrun 转发。pi 与 dsh 被包裹时,包裹命令是进程组的首进程,退出码透传运行时的退出码。
- 提示词写入标准输入后关闭(claude-code 写成一行 JSON 消息,见运行页的 claude-code 一节)。运行时的标准错误原样转到 agentrun 的标准错误,同时保留最后 500 个字符供
detail使用。 - 运行时主进程退出后,无论什么原因,agentrun 都向整个进程组发 SIGKILL,再按进程树清理进程组之外的后代进程(见下一条),然后最多等 1 秒读完剩余的输出,输出
end。理由:实测 claude-code 退出后它启动的后台命令仍在运行,而且这些进程持有标准输出时,读到 EOF 要等它们退出。用waitid判断主进程退出,不用标准输出的 EOF 判断。 - 按进程树清理:pi 的 bash 工具和沙箱外的 claude-code 用 setsid 启动命令,命令在新会话里,不在进程组内,进程组的 SIGKILL 发不到。Linux 上 agentrun 启动时用
prctl(PR_SET_CHILD_SUBREAPER)把自己设为 child subreaper,运行时退出后留下的孤儿进程改挂到 agentrun 下,不挂到 1 号进程;主进程退出后,agentrun 读一遍/proc/<进程号>/stat里的父进程号,以主进程和自己为根找出全部后代,逐个 SIGKILL,最多等 200 毫秒回收它们,再查一遍进程树,至多三轮。运行期间不做任何事。macOS 没有 subreaper,孤儿进程挂到 launchd,退出后找不回来,而且命令常常启动后台进程后立刻退出(例如 node 的spawn加unref),后台进程在几毫秒内就成了孤儿,所以 agentrun 用 kqueue 的EVFILT_PROC加NOTE_FORK监视主进程和它的每一个后代:任何一个被监视的进程 fork 时,agentrun 立刻用 libproc(proc_listallpids与proc_pidinfo,不启动ps)读一遍进程表,把新出现的后代记下来并加入监视,另外每 1 秒也读一遍兜底。记录的内容是进程号、启动时间和进程组号:进程号被回收并分给无关进程时启动时间对不上,就不再当作本次运行的进程;进程组号在后台进程换了父进程之后也不变,XNU 不会把仍被某个进程组占用的号分给新进程。主进程退出后,以仍在运行的记录进程和主进程为根找出后代,连同这些记录进程一起清理,再向记录过的每个进程组发 SIGKILL,组长进程还在但不是记录的进程时跳过这个组。启动前检查期间收到信号时,检查进程及其后代也按同样的方法清理。 - 先清理再回收:
waitid带WNOWAIT,得知主进程退出时不回收它,进程组与进程树清理完之后才回收。理由:主进程被回收后,它的进程号与进程组号可以分给新进程,同时运行的其他程序这时启动的进程会收到本次运行的 SIGKILL。启动前的沙箱检查与 doctor 的检查进程也按这个顺序处理。 - 向进程组发 SIGKILL 后等 5 毫秒,再读一遍进程表,组里还有进程就再发一次,至多三轮。理由:macOS 上组里的进程恰好在
killpg执行时 fork,新的子进程可能收不到这次信号而留下,它持有标准错误时 agentrun 读不到 EOF(macOS 复现);紧接着killpg读进程表时,正在创建的子进程可能还不在表里,所以先等一轮再读。 - 收到 SIGINT 或 SIGTERM:运行时启动前收到时不启动,直接输出
interrupted;运行中收到第一个信号时,把同一个信号转发给进程组,等待 5 秒,到时仍未退出就 SIGKILL;等待中再收到信号立即 SIGKILL。等待期间运行时的输出照常解析。 - 创建会话临时目录、适配器的
launch(codex 在其中创建私有配置目录)与启动运行时这三步进行中收到的信号,在这一步完成后处理(Signals::hold):目录已经登记,会被删除;运行时已经进入运行阶段,信号按运行中的规则转发,事件流是start、prompt、end interrupted。创建目录或launch失败时结果是rejected(退出码 2),启动运行时失败时结果是failed(退出码 1),两种情况下运行先进入结束阶段,推迟处理的信号被忽略,输出的只有这一个失败的end。理由:信号在目录创建之后、登记之前到达时,目录会留下;在运行时启动之后、进入运行阶段之前到达时,macOS 上刚启动的运行时可能继续运行。 --timeout到时:向进程组发 SIGTERM,同样等待 5 秒后 SIGKILL。超时后的等待中又收到信号,结果是interrupted。- 超过
--max-turns或--max-tokens:与超时相同,见轮数与 token 上限。Signals::stop_for_limit只在运行时还没有被终止时发 SIGTERM;已经因为超时或信号进入等待时不做任何事,返回 false,这时结果不是limit,理由是再发一次终止信号会被当作第二个信号,立即 SIGKILL;运行时已经退出、剩余输出还在读时不发信号,返回 true,结果是否为limit按 end.status 的判定。理由:主进程退出与读完输出的先后不固定,结果不能随之变化。 - pi 与 dsh 被
bwrap包裹时,第一个信号(转发的 SIGINT 或 SIGTERM,以及超时与上限的 SIGTERM)只发给 bwrap 的子进程(pi 或 dsh),不发给进程组:agentrun 读/proc/<bwrap 进程号>/task/<bwrap 进程号>/children找到它,读不到时扫描/proc/*/stat找父进程是 bwrap 的进程;都找不到时仍向进程组发信号。之后的 SIGKILL 仍发给整个进程组。理由:bwrap 0.9.0 不转发 SIGTERM,向进程组发信号时 bwrap 自己退出,--die-with-parent随即 SIGKILL 被包裹的 pi,pi 没有宽限期,正在改写的auth.json可能写坏(云端实测)。dsh 按同样的规则处理,让它在宽限期内结束。运行时自己的子进程收不到第一个信号,由最后的 SIGKILL 清理。 - 结束顺序:输出
end,删除临时目录,按退出码表退出。agentrun 不重新给自己发信号。
是否被中断只看 agentrun 自己收到的信号与自己的计时,不看运行时的退出码与事件。理由:实测 claude-code 收到 SIGINT 后以退出码 0 结束,SIGTERM 后以 143 结束且没有 result;pi 收到 SIGINT 后以 130 结束,最后没有 message_end;codex 收到 SIGTERM 后在 macOS 上以 143 结束,在 Linux 上以 0 结束;dsh 收到 SIGINT 后以 130 结束,SIGTERM 后以 0 结束,两种情况都没有 turn_end。
已知限制:macOS 上一个进程在 fork 之后、agentrun 读到进程表之前(约 1 毫秒)就退出时,它刚启动的子进程记录不到;子进程还在同一个进程组里时仍会被清理。macOS 上进程组的 SIGKILL 与组里进程的 fork 同时发生、并且 agentrun 读进程表时新的子进程还没有出现在表里,这个子进程会留下;doctor 的检查进程不做进程树监视,这种情况下没有其他兜底。
多个运行同时进行
同一个用户可以同时启动多个 agentrun,运行时可以相同也可以不同,--cwd 也可以相同。agentrun 不在运行之间加锁,也不限制同时运行的数量。
每次运行各自独立的部分:
- 会话临时目录与 codex 的私有配置目录:每次运行新建,名字带随机后缀,结束时只删除自己的。dsh 的私有目录在会话临时目录里,几次 dsh 运行之间不共用会话记录与设置。
- 过滤代理:每次运行在自己的进程里启动一个代理,端口随机,Unix socket 在自己的会话临时目录里。沙箱里的进程只能连接本次运行的代理(Linux 上在独立的网络命名空间里,macOS 上 Seatbelt 只放行这一个端口),
network事件只含本次运行的请求。 - 进程清理:只作用于本次运行的进程组和本次运行的进程树。Linux 上孤儿进程挂到最近的 subreaper 祖先,也就是启动它的那个 agentrun;macOS 上只清理本次运行记录过的进程。
- 沙箱的启动前检查:每次运行各自执行一次,检查进程按本页的顺序清理。
共用的部分:
- 登录文件:pi 的几个运行直接读写用户状态目录里的同一个
auth.json;codex 每次运行的私有目录里都是指向用户同一个登录文件的符号链接。容器里多次运行同时用AGENTRUN_CODEX_AUTH或AGENTRUN_PI_AUTH写入时,由目录锁保证登录文件与旁注一致,见凭据。 - 令牌刷新:两个运行时都在访问令牌离过期不到 5 分钟时刷新,刷新前重新读一次登录文件,文件已被别的进程更新就不再刷新。pi 刷新前在用户状态目录的
auth.json.lock上加锁,几次运行之间、运行与用户自己的 pi 之间共用这一把锁,后拿到锁的运行重新读到新令牌后不再刷新。codex 只有进程内的锁,几次运行在令牌快过期时同时启动,会用同一个刷新令牌各刷新一次。服务商轮换刷新令牌时,后到的刷新通常被拒绝,这次运行不写登录文件:codex 收到 401 后重新读登录文件,先到的运行已写入新令牌时继续,否则以refresh token was already used. Please log out and sign in again.结束。两个运行时都原地改写文件,codex 的符号链接不会被换成普通文件(pi 1.0.4 与 codex 0.159.2 源码)。 - claude-code 的
~/.claude.json:agentrun 不隔离它,同时运行的 claude-code 都读写这个文件。 - macOS 上 claude-code 沙箱里命令的
TMPDIR是/tmp/claude-<uid>,同一用户的所有 claude 会话共用,见会话临时目录。 - 相同的
--cwd:几个 agent 都能写这个目录,agentrun 不协调它们的写入,同一个文件被几个 agent 改写时,结果取决于写入的先后。