Skip to content

dl carries two tokens into a workspace and nothing else, so no session manager can see the agent it started #548

Description

@blooop

The gap

Every session manager worth using — herdr, Agent View, gh-issues-tui, the
tmux-and-worktree family — is blind to an agent dl started, and all of them
fail the same way for the same reason. This is the priority: dl is the thing
that can fix it, once, for all of them.

A manager learns what an agent is doing by one of two routes:

  1. Process and screen inspection on the host. Under dl the foreground
    process is ssh and the pane is an ssh stream, so the process half finds no
    agent. (The screen half may fare better than expected — see the measurement.)

  2. A hook the agent runs, reporting to the manager. herdr's is
    representative and is the honest general case: a command, with coordinates in
    the environment, talking to a unix socket:

    "$HERDR_BIN_PATH" pane report-agent "$HERDR_PANE_ID" --state working

    with HERDR_BIN_PATH, HERDR_PANE_ID, HERDR_SOCKET_PATH, HERDR_ENV=1.

Route 2 is the one that can work through a container, and dl blocks all three
of its parts: the binary is a host path, the socket is a host socket, and the
coordinates are not forwarded. dl forwards exactly two environment variables,
both hardcoded and both credentials (GH_TOKEN, CLAUDE_CODE_OAUTH_TOKEN), and
forwards no sockets at all.

The failure is silent every time, which is what makes it expensive: with repos
shipping the claude-code feature, ~/.claude/hooks is mounted read-only into
the container, so the manager's hook script is present and does run — and
then finds nothing to talk to.

The mechanism is already here, and it was built anticipating this

Two facts from clients/ssh.rs change the shape of this from "new subsystem" to
"widen an existing one":

The permit list is already a list. send_env: &[String] is threaded through
to the ssh argv as -o SendEnv=<name> per entry, and into control_key, which
length-prefixes every field with a comment written against exactly this change:

a collision here is exactly the silent cross-permit-list reuse this key exists
to prevent. The prefixes are what make that impossible by construction rather
than by an argument about what an alias or an environment variable name is
allowed to contain — which is the kind of argument that stops holding the day
somebody widens one of them.

Today the list holds one name. Widening it is the mechanism working as designed,
and master reuse is already keyed on it, so two different permit lists cannot
share a socket.

The transport is real OpenSSH, so sockets can cross without a mount.
PROGRAM = "ssh", over the alias devpod publishes whose ProxyCommand tunnels
devpod ssh --stdio. That means -R /container/path:/host/path unix-socket
forwarding is available on this path.

That supersedes the bind mount proposed in #547, and is strictly better:
a bind mount lands only at container creation, so every existing workspace would
need dl <ws> recreate. A remote forward is negotiated per connection, so it
works on attach, on a container that has been running for a week, with nothing
to recreate. #547 should become a consumer of this map rather than its own
design.

Proposed shape

Two switches, both taking values, in the style of DEVLAUNCH_AID_AGENT rather
than the boolean ones:

Variable What it does
DEVLAUNCH_FORWARD_ENV=NAME[,NAME...] Add names to the ssh SendEnv permit list for this launch
DEVLAUNCH_FORWARD_SOCKET=/host/path[:/container/path] Forward a unix socket into the workspace with ssh -R

Plus reuse of the existing lend path (the one that streams gh and claude
in as a tar over the channel already held) for the manager's own CLI, so
$..._BIN_PATH resolves inside.

Set those three and any manager's hook protocol works, with no per-manager code
in dl and no cooperation from the manager. herdr is the first consumer; nothing
about the facility knows its name.

Measure first

None of this is checked; there is no herdr, devpod or docker in the environment
this was written from. An agent on the machine should answer these before any
code
, and the first two can each void the design.

  1. Does the sshd accept arbitrary names? SendEnv is only half a handshake —
    the server must AcceptEnv them or they are dropped silently. GH_TOKEN
    arrives today, so either devpod's sshd sets AcceptEnv * or something
    configures it. Find out which, because "it works for GH_TOKEN" does not imply
    it works for HERDR_PANE_ID. Check the container's /etc/ssh/sshd_config
    and confirm a forwarded novel name actually lands in the remote environment.
  2. Does -R unix forwarding survive the ProxyCommand? Forward any socket
    into a live workspace and connect to it from inside. A ProxyCommand
    transport carries the ssh protocol, so remote forwards should work, but this
    has never been exercised here.
  3. Does the forward interact with ControlMaster? A remote forward belongs
    to the master connection, so a later trip joining an existing master does
    not get its own forward. control_key must take the forward spec as a
    field exactly as it takes send_env, or a second launch silently inherits the
    first's socket — the precise failure the length-prefixing comment describes.
  4. Is screen detection already working? Cheapest possible outcome, and worth
    testing before building anything. dl <ws> -- claude pipes the agent's own
    TUI through the pane, so a manager matching rules against screen content may
    already classify correctly and only the process-name half may be failing. If
    so, herdr's HERDR_AGENT=<agent> (documented for "VMs and wrappers") set
    host-side may be the entire fix, at zero cost to dl.

If (4) answers yes, most of this map is unnecessary. Run it first.

Scope and constraints

  • A credential is not a coordinate. The two existing forwards are secrets
    and go through a private file and a permit list for that reason. These are
    not, but the same care applies in reverse: an arbitrary caller-supplied name
    list must not become a way to sweep the host's environment into a container a
    repo you did not write can read. Decide whether names are allowlisted by
    pattern, and say why in the code.
  • Which transports get it. There are two: real ssh for anything needing a
    pty, and the devpod transport for one-shots. Socket forwarding exists only on
    the first. Say plainly which launches carry the facility.
  • Off by default, no launch pays for an unused forward, and a stale =0 turns
    nothing on. Pinned by a test, as DEVLAUNCH_ZELLIJ is.
  • The README env table and docs/workspace-tools.md gain the variables;
    flows::provision::lending_contract reads that file by heading, and
    test_docs_prose.py bans em and en dashes there.

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions