Skip to content

Ambient $PTY_ROOT silently overrides a catalog's declared pty-root, so an isolated catalog can spawn agents into the live one #223

Description

@schickling-assistant

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:

  1. Declared pty-root wins over ambient $PTY_ROOT when a catalog declares one. The
    environment variable remains the default for catalogs that do not.
  2. Refuse on conflict: if a catalog declares a pty-root and $PTY_ROOT disagrees, fail closed
    with both values named, rather than silently choosing.
  3. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:catalogCatalog structure, declarations, transactions, and admission · Set: manualarea:ptyPTY sessions and terminal integration · Set: manualorigin:agentFiled or primarily produced by an AI agent · Set: manualtype:bugSomething broken or a regression · Set: manual

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions