Summary
Ambient $PTY_ROOT overrides a catalog's own declared catalog { pty-root … } for st2 up,
st2 tasks and st2 doctor. An isolated catalog that explicitly declares its pty root can still
spawn its agents into a different root — including a production one — purely because an
environment variable was inherited from the invoking shell. Nothing warns.
Read at 3e01294.
Reproduction
# An isolated catalog that declares its own pty root.
mkdir -p /tmp/iso/catalog /tmp/iso/pty
cat > /tmp/iso/catalog/catalog.kdl <<'KDL'
catalog {
pty-root "/tmp/iso/pty"
}
KDL
# ... plus one agent declaration under agents/<host>/<id>/agent.kdl
# Invoked from a shell whose PTY_ROOT points somewhere else — e.g. any shell already
# set up for a different (production) catalog:
PTY_ROOT=/var/lib/other/pty st2 up --catalog /tmp/iso/catalog --host "$(hostname)" --once
The agent is created under /var/lib/other/pty, not the declared /tmp/iso/pty. The declared
pty-root is silently ignored.
I hit this while building an isolated experiment fixture and it put a probe agent into the live pty
root of a host running real managed agents. No warning, no error, exit 0.
Why it matters
Isolation is the guarantee the declared pty-root exists to provide. Anyone writing a test fixture,
an experiment catalog, or an eval cell will reasonably assume that declaring it is sufficient — the
whole point of putting it in the declaration is that it is not the ambient environment's business.
The failure is silent and lands in the one place it must not.
The general shape: for these commands the declaration is not authoritative over the environment.
Suggested direction
Any one of these would have prevented it, in rough order of preference:
- Declared
pty-root wins over ambient $PTY_ROOT when a catalog declares one. The
environment variable remains the default for catalogs that do not.
- Refuse on conflict: if a catalog declares a
pty-root and $PTY_ROOT disagrees, fail closed
with both values named, rather than silently choosing.
- At minimum, warn loudly and state which root was chosen and why.
Two related isolation hazards, same theme
Filing these here rather than as separate issues since they share a cause — ambient state winning
over an explicit scope.
st2 hooks install accepts --catalog but ignores it for the selection. It writes the hook-set
selection receipt to shared ambient state ($XDG_STATE_HOME/st2/hooks/current.json), so running
it against an isolated catalog can retarget the hook set for every agent on the host. The
content-addressed set directory being shared is reasonable; the selection being global while the
command accepts a --catalog that does not scope it is the surprise. In my case it happened to
re-select the already-selected set, so there was no live effect — but that was luck, not design.
An agent declaration with no env block inherits the launcher's environment, including
ST_AGENT. A probe declared as identity "probe" booted with ST_AGENT set to the supervising
operator's identity, inherited through st2 up, and could have written to that agent's bus. A
declaration that names its own identity arguably should not be able to run under someone else's,
even by omission.
Posted on behalf of @schickling
| field |
value |
agent_name |
unknown |
agent_tool |
Claude Code |
agent_tool_version |
2.1.220 |
agent_runtime |
Claude Code 2.1.220 |
agent_session_lookup |
unavailable |
tooling_profile |
dotfiles@unknown-dirty |
Summary
Ambient
$PTY_ROOToverrides a catalog's own declaredcatalog { pty-root … }forst2 up,st2 tasksandst2 doctor. An isolated catalog that explicitly declares its pty root can stillspawn its agents into a different root — including a production one — purely because an
environment variable was inherited from the invoking shell. Nothing warns.
Read at
3e01294.Reproduction
The agent is created under
/var/lib/other/pty, not the declared/tmp/iso/pty. The declaredpty-rootis silently ignored.I hit this while building an isolated experiment fixture and it put a probe agent into the live pty
root of a host running real managed agents. No warning, no error, exit 0.
Why it matters
Isolation is the guarantee the declared
pty-rootexists to provide. Anyone writing a test fixture,an experiment catalog, or an eval cell will reasonably assume that declaring it is sufficient — the
whole point of putting it in the declaration is that it is not the ambient environment's business.
The failure is silent and lands in the one place it must not.
The general shape: for these commands the declaration is not authoritative over the environment.
Suggested direction
Any one of these would have prevented it, in rough order of preference:
pty-rootwins over ambient$PTY_ROOTwhen a catalog declares one. Theenvironment variable remains the default for catalogs that do not.
pty-rootand$PTY_ROOTdisagrees, fail closedwith both values named, rather than silently choosing.
Two related isolation hazards, same theme
Filing these here rather than as separate issues since they share a cause — ambient state winning
over an explicit scope.
st2 hooks installaccepts--catalogbut ignores it for the selection. It writes the hook-setselection receipt to shared ambient state (
$XDG_STATE_HOME/st2/hooks/current.json), so runningit against an isolated catalog can retarget the hook set for every agent on the host. The
content-addressed set directory being shared is reasonable; the selection being global while the
command accepts a
--catalogthat does not scope it is the surprise. In my case it happened tore-select the already-selected set, so there was no live effect — but that was luck, not design.
An agent declaration with no
envblock inherits the launcher's environment, includingST_AGENT. A probe declared asidentity "probe"booted withST_AGENTset to the supervisingoperator's identity, inherited through
st2 up, and could have written to that agent's bus. Adeclaration that names its own identity arguably should not be able to run under someone else's,
even by omission.
Posted on behalf of @schickling
agent_nameagent_toolagent_tool_versionagent_runtimeagent_session_lookuptooling_profile