@sjawhar/claude-envoy 1.11.1 → 1.11.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,5 +2,5 @@
2
2
  "name": "claude-envoy",
3
3
  "description": "Deliver Envoy events as Claude Code channel notifications and expose Envoy MCP tools.",
4
4
  "mcpServers": "./.mcp.json",
5
- "version": "1.11.1"
5
+ "version": "1.11.3"
6
6
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-envoy",
3
- "version": "1.11.1",
3
+ "version": "1.11.3",
4
4
  "description": "Deliver Envoy events as Claude Code channel notifications and expose Envoy MCP tools.",
5
5
  "mcpServers": {}
6
6
  }
@@ -55133,7 +55133,7 @@ var import_nats2 = __toESM(require_mod4(), 1);
55133
55133
 
55134
55134
  // package.json
55135
55135
  var name = "@sjawhar/claude-envoy";
55136
- var version2 = "1.11.1";
55136
+ var version2 = "1.11.3";
55137
55137
  var legion = {
55138
55138
  daemonApiVersion: 54
55139
55139
  };
@@ -14,7 +14,10 @@ limit, and one command that fills it ends every role's turn, not only yours.
14
14
  In the pod you compile and test the packages your change touches, build the program a check of
15
15
  your issue drives (`go build ./cmd/legion`), vet the module you changed (`go vet -tags e2e ./...`,
16
16
  which compiles and runs no test), and, in a package whose tests need Postgres, run only the tests
17
- your change touches by name (`go test -run`).
17
+ your change touches by name (`go test -run`). To run Go's race detector, prefix the command with
18
+ `CGO_ENABLED=1`: `CGO_ENABLED=1 go test -race -count=1 -run '<TestName>' ./internal/<package>/`.
19
+ The worker image sets `CGO_ENABLED=0` and carries gcc (both in
20
+ `packages/daemon/docker/worker.Dockerfile`), and Go refuses `-race` without cgo.
18
21
 
19
22
  A whole module's or whole package's test run goes to CI on the pull request, where a job of that
20
23
  pull request runs it: `go test ./...`, a whole Postgres-backed package such as
@@ -28,14 +31,31 @@ heavy run goes where your deployment's instructions say.
28
31
  file `OMP_SESSION_SQL_DSN_FILE`: that is the live sessions database every pod shares, and it
29
32
  holds every role's transcript.
30
33
  - Give every Postgres variable a test reads `$LEGION_TEST_PG_DSN`, your pod's own server:
31
- `LEGION_TEST_PG_DSN`, `DISPATCH_TEST_DATABASE_URL` and `BROKER_TEST_DATABASE_URL`. Each makes
32
- and drops a database of its own per test, so your pod's roles can share the server.
34
+ `LEGION_TEST_PG_DSN`, `DISPATCH_TEST_DATABASE_URL` and `BROKER_TEST_DATABASE_URL`. Most suites
35
+ make and drop one of their own per test on that server — a database through `internal/testpg`,
36
+ a schema through `internal/broker/store/storetest`'s `URL` — so your pod's roles can share the
37
+ server. Four write the database the URL itself names instead, and take a database of their own
38
+ from the next bullet's `createdb` line: `packages/envoy/internal/broker/store`, whose
39
+ `store_test.go` helpers call `Open(ctx, testDatabaseURL(t))` and then `Migrate` on the database
40
+ `BROKER_TEST_DATABASE_URL` names; and three that open the database `DISPATCH_TEST_DATABASE_URL`
41
+ names and migrate it, `packages/envoy/internal/dispatch/store` (`openTestStore`, in its
42
+ `store_test.go`), `packages/envoy/internal/dispatch/delivery` (`deliveryTestPool`, which also
43
+ deletes every delivery table's rows) and `packages/envoy/internal/dispatch/redeliver`
44
+ (`redeliverTestLeases`). In an empty database of its own, one run of the broker's left 24 tables
45
+ on 2026-11-06, and one whole run of the Dispatch store's left 104 on 2026-11-07, a count that
46
+ grows with Dispatch's migrations.
33
47
  - Anything that migrates into the database its DSN names, rather than making one per test, needs
34
48
  a database of your own, because a sibling role writing the same one collides with you: the
35
49
  Dispatch browser suite's `DATABASE_URL`, which resets what it is given, and the stage scripts'
36
50
  `LEGION_E2E_PG_DSN`, which a daemon's `postgres_dsn` becomes and migrates Legion's schema into.
37
51
  Make one and point the variable at it:
38
52
  `createdb -h 127.0.0.1 -p 5433 -U legion_test "e2e_$(date +%s)"`.
53
+ The DSN's own database is owned by your login and writable, so what keeps two roles out of it is
54
+ this rule rather than the server. `packages/daemon/internal/daemon` is the third case, whose
55
+ `TestMain` takes one database for the package's run through `internal/testpg/testdb`.
56
+ Empty the served database by dropping your own suite's schemas and your own objects in `public`,
57
+ never `public` itself: `vector` goes with it, your login may not create it again, and
58
+ `ensure` only ever installs it at a database's creation.
39
59
  - The server is empty in every new pod and dies with it. Every database made from `template1`
40
60
  has pgvector.
41
61
  - `LEGION_TEST_PG_DSN` set and the connection refused: that is not yours to repair. Starting the
@@ -48,12 +68,14 @@ heavy run goes where your deployment's instructions say.
48
68
  is starting one and nothing will appear while you wait. With `legion-testpg` on `PATH`, start
49
69
  one yourself on the issue's volume: the directory it defaults to is a 1 MiB filesystem your
50
70
  role's own mounts cover, so `export LEGION_TESTPG_DIR=$XDG_DATA_HOME/testpg` first, then
51
- `export LEGION_TEST_PG_DSN=postgres://legion_test@127.0.0.1:5433/postgres?sslmode=disable`
52
- and `legion-testpg ensure`, which makes the cluster, the role and `vector` in `template1`. That
71
+ `export LEGION_TEST_PG_DSN=postgres://legion_test@127.0.0.1:5433/legion_test?sslmode=disable`
72
+ and `legion-testpg ensure`, which makes the cluster, the role, `vector` in `template1` and the
73
+ database the DSN names, owned by that role. That
53
74
  server lives in your generation: it dies when your generation ends, which takes a sibling
54
75
  role's connections and databases with it mid-test, and the next `ensure` of either role finds
55
- the same cluster on disk and serves it again. With no
56
- `legion-testpg` either, stand up one with the harness below (**The worked case**) and set the DSN.
76
+ the same cluster on disk and serves it again. With no `legion-testpg` either, stand up one with
77
+ the harness of `skill://legion-worker/references/withheld-capability.md` (**The worked case**)
78
+ and set the DSN.
57
79
 
58
80
  ## Reading a shared database
59
81
 
@@ -160,169 +182,23 @@ So that writable `$HOME`, and the issue's disk, are where an install for yoursel
160
182
  the mechanics the answer is not a flat no: `redis-cli`, `sqlite3` and `dig` did run from an
161
183
  extraction prefix under `$HOME` with `LD_LIBRARY_PATH` set, and `psql` did not — Debian's
162
184
  `/usr/bin/psql` is a Perl wrapper that loads `PgCommon.pm` through Perl's own search path, which no
163
- extraction prefix is on. What rules such an attempt out is not the unpacking: it is the next
164
- section's test ("The test: where it lands, and what it leaves resolving"), and an extraction under
165
- `$HOME` fails that test's first condition whatever you scope it to, because `/home/legion` is the
166
- pod's disk rather than the issue's volume — the placement `roleEnvironment`
167
- (`packages/daemon/internal/runtime/sandbox/manifest.go`) exists to move writes off. The sanctioned
168
- extraction is the harness's: onto the issue's volume, and exported around its own commands and no
169
- further (the Dispatch e2e stack, below, is the worked case). For everything the image does not
170
- carry, the one documented step is a pinned run through one of the three package runners it does
171
- — plus `uv run --with` when what you need is a library rather than a command — and a tool
172
- no runner reaches is an image change ("When no runner reaches it", below).
185
+ extraction prefix is on. What rules such an attempt out is not the unpacking: it is the test of
186
+ `skill://legion-worker/references/withheld-capability.md#the-test-where-it-lands-and-what-it-leaves-resolving`,
187
+ and an extraction under `$HOME` fails that test's first condition whatever you scope it to,
188
+ because `/home/legion` is the pod's disk rather than the issue's volume — the placement
189
+ `roleEnvironment` (`packages/daemon/internal/runtime/sandbox/manifest.go`) exists to move writes
190
+ off. The sanctioned extraction is the harness's: onto the issue's volume, and exported around its
191
+ own commands and no further (the Dispatch e2e stack of that reference is the worked case). For
192
+ everything the image does not carry, the one documented step is a pinned run through one of the
193
+ three package runners it does — plus `uv run --with` when what you need is a library rather than
194
+ a command — and a tool no runner reaches is an image change ("When no runner reaches it", below).
173
195
 
174
196
  ## What the withheld capability is, and what it is not
175
197
 
176
- The withheld row reads `installs or configures its own dependencies in the pod: Legion owns its
177
- dependencies` (`packages/daemon/internal/capabilities/capabilities.go`, ruling
178
- dispatch://LEGION-200). Read as a sentence on its own, that wording would also forbid `uvx`,
179
- `bunx --package` and `go run`, each of which fetches a package and writes a cache. It does not, and
180
- three artefacts in this repository are what draw the line — not a claim about what the ruling meant:
181
-
182
- - **What the image ships for a role to use.** `packages/daemon/docker/worker.Dockerfile` installs
183
- `uv` and `uvx`, `bun` with its `bunx` link, and the Go toolchain, and the `toolchain` capability
184
- row (`packages/daemon/internal/capabilities/capabilities.go`) grants them by name. A runner put
185
- in the image deliberately and granted by a capability row is not a route around a withheld one.
186
- - **What the pod configures for those runners to write.** A role's environment relocates
187
- `UV_CACHE_DIR` and `UV_PYTHON_INSTALL_DIR` (`podkind.go`) and `BUN_INSTALL_CACHE_DIR`, `GOCACHE`
188
- and `GOMODCACHE` (`roleEnvironment`, `packages/daemon/internal/runtime/sandbox/manifest.go`)
189
- onto the issue's volume, shared by the issue's roles. Legion provisions the caches a pinned run
190
- fills; filling them is what they are for.
191
- - **What Legion itself does at launch.** `installBlock`
192
- (`packages/daemon/internal/runtime/sandbox/plugins.go`) renders a `bun add` under
193
- `/usr/bin/flock` into that shared bun install cache on every installing launch. Fetching a pinned
194
- package at run time is something Legion does, not something it withholds.
195
-
196
- So the withheld row is about a dependency **the pod carries**: something that outlives the command
197
- that created it, and that a later launch or another role's process then resolves *without asking
198
- for it*.
199
-
200
- ### The test: where it lands, and what it leaves resolving
201
-
202
- An install of your own — an interpreter, a browser, a second toolchain, an extraction prefix — is
203
- on the sanctioned side when both of these hold: what it writes lands on the issue's own volume,
204
- under `/legion`, which outlives a role's relaunch in its pod and is shared by the issue's roles —
205
- whether through a variable the runtime sets for you there or under a path on that volume your
206
- harness names itself — but outside the issue's workspace (`$LEGION_WORKSPACE`, under
207
- `/legion/workspaces`) and the runtime's own directories (`/legion/repos`, `/legion/feed`); and
208
- after the command exits nothing else resolves differently. The workspace is excluded because its jj
209
- working copy tracks every new file under jj's `snapshot.max-new-file-size` (1 MiB for a role), so
210
- a root written there is added to the change every role shares, and a larger new
211
- file is left untracked, every jj command in the workspace then warning `Refused to snapshot some
212
- files`. The one write into it that is sanctioned is a repository's own dependency install into
213
- its ignored directories, below. The variables the runtime sets on the volume, where a tool's own
214
- cache already goes, are
215
- `XDG_CACHE_HOME` (`/legion/scratch/cache`), `XDG_DATA_HOME`, `XDG_STATE_HOME`, `TMPDIR`, `GOCACHE`,
216
- `GOMODCACHE`, `BUN_INSTALL_CACHE_DIR`, `UV_CACHE_DIR` (`/legion/uv/cache`) and
217
- `UV_PYTHON_INSTALL_DIR` (`/legion/uv/python/<issue>`). (`XDG_CONFIG_HOME` is set too and is not
218
- one of them: it is your role's own config home, a copy of the pod's made for your role on the
219
- pod's disk, which no other role reads.) `$HOME` and `/tmp` are not on the volume:
220
- both are your role's own, on the pod's disk.
221
-
222
- It is the withheld capability when either condition fails — anything that writes into a
223
- system prefix, and anything that leaves a toolchain, shim or binary which a later launch or
224
- another role's process will resolve without asking for it. Between two installs that both land in
225
- the right place, the test is resolution, not disk, and it reads as scope: a second toolchain
226
- exported for the commands of your own run — by absolute path, or on a `PATH` your harness's own
227
- script sets around its own invocations — is sanctioned; the same toolchain left where a later
228
- launch, another role or a subagent picks it up without naming it is operator setup, whoever typed
229
- the command.
230
-
231
- What each shape leaves resolving:
232
-
233
- - A pinned runner run: nothing. `uvx --from httpie@3.2.4 http --version` leaves a cache entry
234
- addressed by that coordinate and that version, which the next command reaches only by writing the
235
- same pin; no `PATH`, no interpreter and no library search of any other process changes. That is
236
- the sanctioned side, and it is the side the forms below are on.
237
- - A repository's own dependency set: nothing either, which is why the caches are provisioned at
238
- all. `uv sync` or `bun install` in your workspace materialises what that project already
239
- declares, into that project's own directory, which a process reaches by asking for that project
240
- and no other way — and the runtime puts `UV_PYTHON_INSTALL_DIR` and the workspace itself on the
241
- issue's volume exactly so every role of the issue's pod reuses it.
242
- - An extraction prefix: whatever you leave resolving through it, which is yours to decide. Unpack
243
- Debian packages under `$HOME`, export `LD_LIBRARY_PATH` in the shell you go on working in, and
244
- you change what every later command in that shell — and every subagent that inherits the
245
- environment — resolves `psql` or its libraries to, asked for or not, on a prefix nothing records,
246
- pins or rebuilds: that fails both conditions, and it is the withheld capability. The harness
247
- below is the shape that passes. The opposite shape is in the tree too, and superseded: telling
248
- every subagent to prepend a directory to `PATH` because a subagent's shell starts without it
249
- hands over an environment to processes that never named it. That is the difference between
250
- standing a harness up and getting yourself a CLI.
251
-
252
- The test is about what a command leaves for other processes, never about getting the same bytes
253
- twice — because a pinned line does not promise the same bytes. What a pin binds is the named
254
- distribution at that version; its dependency closure is resolved when the command runs. With
255
- `UV_CACHE_DIR` emptied, `uvx -v --from httpie@3.2.4 http --version` logs `Selecting:
256
- requests==2.34.2 [compatible]` and resolves `urllib3` and `certifi` from ranges, where the warm run
257
- prints `3.2.4` at once off the cache that first run filled. When your work needs the closure pinned
258
- too — a comparison that has to hold across days or across pods — that is a project's own lockfile
259
- with `uv run` or `bun install` in a workspace, not a one-off runner line.
260
-
261
- A pinned run whose tool then installs something of its own — an interpreter, a browser, a second
262
- toolchain — is where the first condition does the work. `uv run --with pyyaml==6.0.2 --no-project`
263
- passes both: the wheel lands in `UV_CACHE_DIR`, under `wheels-v6/pypi/pyyaml/6.0.2-…`, and
264
- `UV_PYTHON_INSTALL_DIR` is never created, because the image's own `python3` served the run. The
265
- interpreter is the other run: a project whose `.python-version` names an interpreter uv must fetch
266
- puts about 70 MB under `UV_PYTHON_INSTALL_DIR`, where every role of the issue's pod finds it, while
267
- the cache holds no wheel. A browser download passes too, and for Playwright — the browser driver this
268
- repository's own Dispatch suite installs (`playwright install`, `packages/dispatch`'s
269
- `e2e:install`) — it passes as it stands, with nothing pointed anywhere: playwright-core resolves
270
- its browser registry under `XDG_CACHE_HOME`, which the runtime has already put on the issue's
271
- volume, so the issue's roles share one registry under the exclusive install lock that
272
- `docs/kubernetes.md` writes down in "Anatomy of a Sandbox pod", the paragraph on sharing the cache
273
- home — worth reading before an install, for the two things that follow from sharing. Pointing
274
- `PLAYWRIGHT_BROWSERS_PATH` elsewhere is what costs you: that shared registry, and the lock that
275
- makes a second role installing at the same time wait rather than race. The condition is for a tool
276
- whose cache the runtime does not already place — Puppeteer is one, resolving its browser cache to
277
- `join(homedir(), '.cache', 'puppeteer')` and so to your role's own home on the pod's disk whatever
278
- `XDG_CACHE_HOME` says. For that one, point the tool's own cache variable (`PUPPETEER_CACHE_DIR`
279
- here) at a directory under one of the paths above yourself, since an unset one leaves the download
280
- wherever the tool defaults, which is nowhere this runtime chose.
281
-
282
- Where it lands is what you pay. Under one of those variables it is on the issue's volume: it
283
- outlives a role's relaunch in its pod, and every role of the issue is told the same path — under
284
- every one of those variables but
285
- `XDG_STATE_HOME`, which is `/legion/scratch/state/<role>`, a directory named for your role
286
- (`roleEnvironment`) because Oh My Pi names its browser broker's lock from the state root and an
287
- abstract socket name is one name across every role of the pod, which share its network. It is not
288
- private: every role of the issue can write it, as it can every path above — though it does not
289
- outlive the pod: the issue's disk is the pod's own and is deleted with it, so a later pod of the
290
- issue starts without it. Under
291
- `$HOME` — `/home/legion`, which no volume is mounted on — it is your role's own home, an overlay
292
- over the image's whose writes land on the pod's disk (an `emptyDir` every role's home and `/tmp`
293
- share): one copy per role, per pod, kept across the role's relaunches in that pod and gone when
294
- the pod is replaced, and charged to the pod's ephemeral-storage limit, which every role of the pod
295
- shares and which is smaller than the volume. Moving exactly these writes off that
296
- filesystem is what `roleEnvironment` (`packages/daemon/internal/runtime/sandbox/manifest.go`)
297
- exists for, after GKE Autopilot evicted issue pods that charged their caches to it — so an install
298
- under `$HOME` fails the first condition and is outside the sanctioned form, however little else it
299
- changes. The three artefacts above are the whole of that line's ground. Disk is not: every cache is
300
- already relocated onto the issue's volume, which has the room for them (`docs/kubernetes.md`, "The
301
- tools a role's own work needs", carries the figure), and a sanctioned install is paid once per
302
- issue pod, on the issue's own disk, by the issue's roles together.
303
-
304
- **The worked case, and the one a role actually meets: the Dispatch e2e stack.** A change to
305
- `packages/dispatch` is proved in the pod by driving the specs it touches, one spec a command
306
- (`bun run e2e -- e2e/<one spec file>`); the whole browser suite runs in CI's `dispatch` job on the
307
- pull request. One spec needs the same stack as all of them: a Postgres with pgvector. On a digest
308
- carrying `legion-testpg`, use the pod's own server ("The database your tests use", above) and
309
- unpack nothing. On a digest without it, the way
310
- that stack is stood up here is Debian packages unpacked with `dpkg-deb -x` into one root on the
311
- issue's volume — the five packages and the start sequence are in
312
- `docs/solutions/testing/the-real-dispatch-stack-runs-inside-a-legion-pod-with-no-postgres-no-docker-and-no-root-INFRA-42.md`,
313
- and
314
- `docs/solutions/testing/a-browser-harness-in-an-issue-pod-lives-on-the-issue-volume-INFRA-43.md`
315
- puts it on the volume, moves the caches with it, and writes it as one idempotent script the next
316
- role re-runs. That passes both conditions, and the second is the one to get right: export the
317
- root's `PATH` and `LD_LIBRARY_PATH` around the harness's own commands — `initdb`, `pg_ctl`, the
318
- server, the Playwright run — rather than into the shell you go on working in. Handing the harness
319
- to the next role is the same test passed again, and
320
- `docs/solutions/legion/a-port-held-in-an-issue-pod-is-a-sibling-roles-harness-move-your-run-with-the-five-port-variables-and-your-own-database-and-never-stop-the-holder-LEGION-695.md`
321
- is the illustration: it leaves the server running and records the `LD_LIBRARY_PATH`, `PATH` and
322
- `DATABASE_URL` in its handoff's `trickyParts`, so what crosses to the next role is a note that
323
- role sets deliberately rather than an environment it inherits. The suite shells out to `psql`,
324
- and `psql` being in the image is what makes that harness work now without your extracting a
325
- client for yourself at all.
198
+ What the withheld row says, the three artefacts that draw the line between it and a pinned runner
199
+ form, and the worked harness case are in
200
+ `skill://legion-worker/references/withheld-capability.md`: read it before you unpack, extract or
201
+ install anything the image does not carry.
326
202
 
327
203
  ## The three pinned runner forms
328
204
 
@@ -528,8 +404,9 @@ to its role topic), with the tool, what your phase needs it for, and the runner
528
404
  ruled out. What is not the step is improvising a CLI for your own convenience — a vendored binary,
529
405
  a hand-rolled substitute, or an extraction prefix dropped on your own `PATH`: the architect decides
530
406
  whether the image change is work to schedule. An extraction a harness builds for its own runs is
531
- not improvising and is not what this rules out (the Dispatch e2e stack, above): the two cases part
532
- on "The test: where it lands, and what it leaves resolving", which the convenience case fails.
407
+ not improvising and is not what this rules out (the Dispatch e2e stack of
408
+ `skill://legion-worker/references/withheld-capability.md`): the two cases part on its test,
409
+ "The test: where it lands, and what it leaves resolving", which the convenience case fails.
533
410
 
534
411
  The operator's side of this — why the image carries those five clients rather than anything else,
535
412
  the five priced options with their measurements, and what the clients cost the image — is in
@@ -716,8 +593,9 @@ something to count.
716
593
  `go test -count=1 -timeout 40m ./internal/workflow/` runs whole in the pod, with no docker
717
594
  panic.
718
595
  - **The rest, by package**, for a named run the pod does make. A Postgres unpacked from Debian
719
- packages (the harness under **The worked case**) has only the `C`, `C.utf8` and `POSIX`
720
- locales, so its default collation is byte order, and `internal/dispatch/api`'s
596
+ packages (the harness under **The worked case** of `withheld-capability.md`) has only the
597
+ `C`, `C.utf8` and `POSIX` locales, so its default collation is byte order, and
598
+ `internal/dispatch/api`'s
721
599
  `TestIssueRanksStayOrderedPastLowercaseDigitsAndProductionShapedMoves` refuses such a database
722
600
  by its own message. The pod's own `legion-testpg` server is made with ICU `en-US`, so that
723
601
  test runs there, as it does in CI's `envoy-go` job, whose Postgres has `en_US.utf8`.
@@ -0,0 +1,158 @@
1
+ # What the withheld capability is, and what it is not
2
+
3
+ Part of `skill://legion-worker`, read from `skill://legion-worker/references/tools.md` before you
4
+ unpack, extract or install anything the worker image does not carry. Every path it cites is in
5
+ cybermonklabs/legion.
6
+
7
+ The withheld row reads `installs or configures its own dependencies in the pod: Legion owns its
8
+ dependencies` (`packages/daemon/internal/capabilities/capabilities.go`, ruling
9
+ dispatch://LEGION-200). Read as a sentence on its own, that wording would also forbid `uvx`,
10
+ `bunx --package` and `go run`, each of which fetches a package and writes a cache. It does not, and
11
+ three artefacts in this repository are what draw the line — not a claim about what the ruling meant:
12
+
13
+ - **What the image ships for a role to use.** `packages/daemon/docker/worker.Dockerfile` installs
14
+ `uv` and `uvx`, `bun` with its `bunx` link, and the Go toolchain, and the `toolchain` capability
15
+ row (`packages/daemon/internal/capabilities/capabilities.go`) grants them by name. A runner put
16
+ in the image deliberately and granted by a capability row is not a route around a withheld one.
17
+ - **What the pod configures for those runners to write.** A role's environment relocates
18
+ `UV_CACHE_DIR` and `UV_PYTHON_INSTALL_DIR` (`podkind.go`) and `BUN_INSTALL_CACHE_DIR`, `GOCACHE`
19
+ and `GOMODCACHE` (`roleEnvironment`, `packages/daemon/internal/runtime/sandbox/manifest.go`)
20
+ onto the issue's volume, shared by the issue's roles. Legion provisions the caches a pinned run
21
+ fills; filling them is what they are for.
22
+ - **What Legion itself does at launch.** `installBlock`
23
+ (`packages/daemon/internal/runtime/sandbox/plugins.go`) renders a `bun add` under
24
+ `/usr/bin/flock` into that shared bun install cache on every installing launch. Fetching a pinned
25
+ package at run time is something Legion does, not something it withholds.
26
+
27
+ So the withheld row is about a dependency **the pod carries**: something that outlives the command
28
+ that created it, and that a later launch or another role's process then resolves *without asking
29
+ for it*.
30
+
31
+ ### The test: where it lands, and what it leaves resolving
32
+
33
+ An install of your own — an interpreter, a browser, a second toolchain, an extraction prefix — is
34
+ on the sanctioned side when both of these hold: what it writes lands on the issue's own volume,
35
+ under `/legion`, which outlives a role's relaunch in its pod and is shared by the issue's roles —
36
+ whether through a variable the runtime sets for you there or under a path on that volume your
37
+ harness names itself — but outside the issue's workspace (`$LEGION_WORKSPACE`, under
38
+ `/legion/workspaces`) and the runtime's own directories (`/legion/repos`, `/legion/feed`); and
39
+ after the command exits nothing else resolves differently. The workspace is excluded because its jj
40
+ working copy tracks every new file under jj's `snapshot.max-new-file-size` (1 MiB for a role), so
41
+ a root written there is added to the change every role shares, and a larger new
42
+ file is left untracked, every jj command in the workspace then warning `Refused to snapshot some
43
+ files`. The one write into it that is sanctioned is a repository's own dependency install into
44
+ its ignored directories, below. The variables the runtime sets on the volume, where a tool's own
45
+ cache already goes, are
46
+ `XDG_CACHE_HOME` (`/legion/scratch/cache`), `XDG_DATA_HOME`, `XDG_STATE_HOME`, `TMPDIR`, `GOCACHE`,
47
+ `GOMODCACHE`, `BUN_INSTALL_CACHE_DIR`, `UV_CACHE_DIR` (`/legion/uv/cache`) and
48
+ `UV_PYTHON_INSTALL_DIR` (`/legion/uv/python/<issue>`). (`XDG_CONFIG_HOME` is set too and is not
49
+ one of them: it is your role's own config home, a copy of the pod's made for your role on the
50
+ pod's disk, which no other role reads.) `$HOME` and `/tmp` are not on the volume:
51
+ both are your role's own, on the pod's disk.
52
+
53
+ It is the withheld capability when either condition fails — anything that writes into a
54
+ system prefix, and anything that leaves a toolchain, shim or binary which a later launch or
55
+ another role's process will resolve without asking for it. Between two installs that both land in
56
+ the right place, the test is resolution, not disk, and it reads as scope: a second toolchain
57
+ exported for the commands of your own run — by absolute path, or on a `PATH` your harness's own
58
+ script sets around its own invocations — is sanctioned; the same toolchain left where a later
59
+ launch, another role or a subagent picks it up without naming it is operator setup, whoever typed
60
+ the command.
61
+
62
+ What each shape leaves resolving:
63
+
64
+ - A pinned runner run: nothing. `uvx --from httpie@3.2.4 http --version` leaves a cache entry
65
+ addressed by that coordinate and that version, which the next command reaches only by writing the
66
+ same pin; no `PATH`, no interpreter and no library search of any other process changes. That is
67
+ the sanctioned side, and it is the side the runner forms of
68
+ `skill://legion-worker/references/tools.md` are on.
69
+ - A repository's own dependency set: nothing either, which is why the caches are provisioned at
70
+ all. `uv sync` or `bun install` in your workspace materialises what that project already
71
+ declares, into that project's own directory, which a process reaches by asking for that project
72
+ and no other way — and the runtime puts `UV_PYTHON_INSTALL_DIR` and the workspace itself on the
73
+ issue's volume exactly so every role of the issue's pod reuses it.
74
+ - An extraction prefix: whatever you leave resolving through it, which is yours to decide. Unpack
75
+ Debian packages under `$HOME`, export `LD_LIBRARY_PATH` in the shell you go on working in, and
76
+ you change what every later command in that shell — and every subagent that inherits the
77
+ environment — resolves `psql` or its libraries to, asked for or not, on a prefix nothing records,
78
+ pins or rebuilds: that fails both conditions, and it is the withheld capability. The harness
79
+ below is the shape that passes. The opposite shape is in the tree too, and superseded: telling
80
+ every subagent to prepend a directory to `PATH` because a subagent's shell starts without it
81
+ hands over an environment to processes that never named it. That is the difference between
82
+ standing a harness up and getting yourself a CLI.
83
+
84
+ The test is about what a command leaves for other processes, never about getting the same bytes
85
+ twice — because a pinned line does not promise the same bytes. What a pin binds is the named
86
+ distribution at that version; its dependency closure is resolved when the command runs. With
87
+ `UV_CACHE_DIR` emptied, `uvx -v --from httpie@3.2.4 http --version` logs `Selecting:
88
+ requests==2.34.2 [compatible]` and resolves `urllib3` and `certifi` from ranges, where the warm run
89
+ prints `3.2.4` at once off the cache that first run filled. When your work needs the closure pinned
90
+ too — a comparison that has to hold across days or across pods — that is a project's own lockfile
91
+ with `uv run` or `bun install` in a workspace, not a one-off runner line.
92
+
93
+ A pinned run whose tool then installs something of its own — an interpreter, a browser, a second
94
+ toolchain — is where the first condition does the work. `uv run --with pyyaml==6.0.2 --no-project`
95
+ passes both: the wheel lands in `UV_CACHE_DIR`, under `wheels-v6/pypi/pyyaml/6.0.2-…`, and
96
+ `UV_PYTHON_INSTALL_DIR` is never created, because the image's own `python3` served the run. The
97
+ interpreter is the other run: a project whose `.python-version` names an interpreter uv must fetch
98
+ puts about 70 MB under `UV_PYTHON_INSTALL_DIR`, where every role of the issue's pod finds it, while
99
+ the cache holds no wheel. A browser download passes too, and for Playwright — the browser driver this
100
+ repository's own Dispatch suite installs (`playwright install`, `packages/dispatch`'s
101
+ `e2e:install`) — it passes as it stands, with nothing pointed anywhere: playwright-core resolves
102
+ its browser registry under `XDG_CACHE_HOME`, which the runtime has already put on the issue's
103
+ volume, so the issue's roles share one registry under the exclusive install lock that
104
+ `docs/kubernetes.md` writes down in "Anatomy of a Sandbox pod", the paragraph on sharing the cache
105
+ home — worth reading before an install, for the two things that follow from sharing. Pointing
106
+ `PLAYWRIGHT_BROWSERS_PATH` elsewhere is what costs you: that shared registry, and the lock that
107
+ makes a second role installing at the same time wait rather than race. The condition is for a tool
108
+ whose cache the runtime does not already place — Puppeteer is one, resolving its browser cache to
109
+ `join(homedir(), '.cache', 'puppeteer')` and so to your role's own home on the pod's disk whatever
110
+ `XDG_CACHE_HOME` says. For that one, point the tool's own cache variable (`PUPPETEER_CACHE_DIR`
111
+ here) at a directory under one of the paths above yourself, since an unset one leaves the download
112
+ wherever the tool defaults, which is nowhere this runtime chose.
113
+
114
+ Where it lands is what you pay. Under one of those variables it is on the issue's volume: it
115
+ outlives a role's relaunch in its pod, and every role of the issue is told the same path — under
116
+ every one of those variables but
117
+ `XDG_STATE_HOME`, which is `/legion/scratch/state/<role>`, a directory named for your role
118
+ (`roleEnvironment`) because Oh My Pi names its browser broker's lock from the state root and an
119
+ abstract socket name is one name across every role of the pod, which share its network. It is not
120
+ private: every role of the issue can write it, as it can every path above — though it does not
121
+ outlive the pod: the issue's disk is the pod's own and is deleted with it, so a later pod of the
122
+ issue starts without it. Under
123
+ `$HOME` — `/home/legion`, which no volume is mounted on — it is your role's own home, an overlay
124
+ over the image's whose writes land on the pod's disk (an `emptyDir` every role's home and `/tmp`
125
+ share): one copy per role, per pod, kept across the role's relaunches in that pod and gone when
126
+ the pod is replaced, and charged to the pod's ephemeral-storage limit, which every role of the pod
127
+ shares and which is smaller than the volume. Moving exactly these writes off that
128
+ filesystem is what `roleEnvironment` (`packages/daemon/internal/runtime/sandbox/manifest.go`)
129
+ exists for, after GKE Autopilot evicted issue pods that charged their caches to it — so an install
130
+ under `$HOME` fails the first condition and is outside the sanctioned form, however little else it
131
+ changes. The three artefacts above are the whole of that line's ground. Disk is not: every cache is
132
+ already relocated onto the issue's volume, which has the room for them (`docs/kubernetes.md`, "The
133
+ tools a role's own work needs", carries the figure), and a sanctioned install is paid once per
134
+ issue pod, on the issue's own disk, by the issue's roles together.
135
+
136
+ **The worked case, and the one a role actually meets: the Dispatch e2e stack.** A change to
137
+ `packages/dispatch` is proved in the pod by driving the specs it touches, one spec a command
138
+ (`bun run e2e -- e2e/<one spec file>`); the whole browser suite runs in CI's `dispatch` job on the
139
+ pull request. One spec needs the same stack as all of them: a Postgres with pgvector. On a digest
140
+ carrying `legion-testpg`, use the pod's own server
141
+ (`skill://legion-worker/references/tools.md#the-database-your-tests-use`) and
142
+ unpack nothing. On a digest without it, the way
143
+ that stack is stood up here is Debian packages unpacked with `dpkg-deb -x` into one root on the
144
+ issue's volume — the five packages and the start sequence are in
145
+ `docs/solutions/testing/the-real-dispatch-stack-runs-inside-a-legion-pod-with-no-postgres-no-docker-and-no-root-INFRA-42.md`,
146
+ and
147
+ `docs/solutions/testing/a-browser-harness-in-an-issue-pod-lives-on-the-issue-volume-INFRA-43.md`
148
+ puts it on the volume, moves the caches with it, and writes it as one idempotent script the next
149
+ role re-runs. That passes both conditions, and the second is the one to get right: export the
150
+ root's `PATH` and `LD_LIBRARY_PATH` around the harness's own commands — `initdb`, `pg_ctl`, the
151
+ server, the Playwright run — rather than into the shell you go on working in. Handing the harness
152
+ to the next role is the same test passed again, and
153
+ `docs/solutions/legion/a-port-held-in-an-issue-pod-is-a-sibling-roles-harness-move-your-run-with-the-five-port-variables-and-your-own-database-and-never-stop-the-holder-LEGION-695.md`
154
+ is the illustration: it leaves the server running and records the `LD_LIBRARY_PATH`, `PATH` and
155
+ `DATABASE_URL` in its handoff's `trickyParts`, so what crosses to the next role is a note that
156
+ role sets deliberately rather than an environment it inherits. The suite shells out to `psql`,
157
+ and `psql` being in the image is what makes that harness work now without your extracting a
158
+ client for yourself at all.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/claude-envoy",
3
- "version": "1.11.1",
3
+ "version": "1.11.3",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Claude Code channel plugin for Legion's Envoy bus",
6
6
  "type": "module",