Skip to content

feat(ipc): runner-aware task IPC infra + auto env tracking#430

Draft
wan9chi wants to merge 5 commits into
mainfrom
infra-env-track
Draft

feat(ipc): runner-aware task IPC infra + auto env tracking#430
wan9chi wants to merge 5 commits into
mainfrom
infra-env-track

Conversation

@wan9chi

@wan9chi wan9chi commented Jun 7, 2026

Copy link
Copy Markdown
Member

PR 1 of 2 — splits #410 into a Graphite stack.

Stack:

Establishes the runner ↔ vite IPC channel and the auto env-tracking half of runner-aware caching:

  • vite_task_ipc_shared, vite_task_server, vite_task_client (+napi): the IPC protocol, Rust transport, and embedded napi client vite loads by absolute path (no-op when absent).
  • vite reports getEnv/getEnvs/disableCache to the runner; the fingerprint diffs the tracked env value/glob match-set across runs and reports envs changed on a miss (wording matches manual env config).
  • ignoreInput/ignoreOutput are accepted over IPC but applied as a no-op here — they land in feat(cache): runner-aware auto output tracking + ignore consumption #431.
  • Auto output restoration stays disabled: output: None resolves to a disabled glob config (matching prior behavior), so a cache hit replays recorded output without restoring written files.

The napi type-def emission for the embedded client cdylib is redirected to a discarded sink in its build.rs so it doesn't leak RunnerClient/load into consumer bindings.

🤖 Generated with Claude Code

wan9chi commented Jun 7, 2026

Copy link
Copy Markdown
Member Author

@socket-security

socket-security Bot commented Jun 7, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedcargo/​napi@​3.9.08210093100100
Addedcargo/​napi-build@​2.3.29810093100100
Addedcargo/​napi-derive@​3.5.69910093100100

View full report

wan9chi added a commit that referenced this pull request Jun 11, 2026
… command (#436)

## Motivation

The planning context's env map — the full session environment, typically
hundreds of entries — was copied at every boundary it crossed during
planning:

- once into `PlanContext` per plan (`plan_query`),
- once **per `&&`-item** by `PlanContext::duplicate` (each command gets
a scoped context),
- once more per `&&`-item into `ScriptCommand::envs` for program lookup
and plan-request callbacks,
- and once per spawn into `all_envs`.

Of these, only the last one does real work — the spawn env is filtered
in place by `EnvFingerprints::resolve`. Every other copy duplicates an
almost-always-identical map just to hand it to the next stage, and the
cost scales with task count × command count, not with actual env
changes.

The immediate trigger is the upcoming runner-aware caching work
(#430/#431): it saves each spawn's **full env context** into the plan
(`SpawnCommand::full_envs`) so `getEnv`/`getEnvs` and tracked-env
validation resolve against the plan rather than the live process env.
Without sharing, that would add yet another full map copy per spawn;
with this change it costs a pointer.

## Approach

Wrap the map in `Arc` with copy-on-write semantics:

- Duplication and hand-off points (`duplicate`, `ScriptCommand::envs`,
`PlanContext::new`) share the map — O(1).
- The two mutation points pay for a copy only when they change
something: `prepend_path` once per task node (package `.bin` PATH
prepend), and `add_envs` only when a command actually has prefix envs —
an empty prefix set is guarded so it doesn't break sharing.
- The per-spawn `all_envs` clone remains, with a comment marking it as
the one place the map's contents are genuinely copied.

No behavior change; `Session` already held the map in an `Arc`, so its
call sites are untouched.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
wan9chi added a commit that referenced this pull request Jun 11, 2026
…ape (#437)

## Motivation

`SpawnFingerprintChange` carried three env variants — `EnvAdded`,
`EnvRemoved`, `EnvValueChanged` — that encoded the same concept three
times over: an env var differing between a stored fingerprint and the
current state. Every consumer had to spell out the same three-arm match,
and the user-facing wording lived inline in `format_spawn_change`'s
arms.

This shape is about to be needed in more places. The runner-aware
caching work (#430) detects env differences at two additional points —
tool-tracked envs (`getEnv`) and env-glob match-sets (`getEnvs`)
validated at cache lookup — and without a shared type each would
re-invent the added/removed/changed triple plus its own formatting, with
the wording drifting across three copies (the first draft of #430 had
exactly that: an option-pair `{old: Option<Str>, new: Option<Str>}` with
an impossible `(None, None)` state, a three-map `EnvGlobDiff`, and two
hand-rolled renderers).

## Approach

Introduce `EnvMismatch { Added, Removed, Changed }` next to the other
mismatch vocabulary in `cache/`, with its `Display` impl as the single
source of the user-facing wording. `SpawnFingerprintChange` folds its
three variants into one `Env(EnvMismatch)`.

No behavior change: detection logic, message strings, and snapshots are
identical. #430 then reuses the enum for its post-run paths instead of
defining parallel shapes.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
wan9chi and others added 5 commits June 11, 2026 22:55
Establishes the runner ↔ vite IPC channel and the auto env-tracking half
of runner-aware caching:

- `vite_task_ipc_shared`, `vite_task_server`, `vite_task_client`(+napi):
  the IPC protocol, Rust transport, and embedded napi client vite loads
  by absolute path (no-op when absent).
- vite reports `getEnv`/`getEnvs`/`disableCache` to the runner; the
  fingerprint diffs the tracked env value/glob match-set across runs and
  reports `envs changed` on a miss (wording matches manual env config).
- `ignoreInput`/`ignoreOutput` are accepted over IPC but applied as a
  no-op for now — they land with auto output tracking.
- Auto output restoration stays disabled: `output: None` resolves to a
  disabled glob config (matching prior behavior), so a cache hit replays
  recorded output without restoring written files.

The napi `type-def` emission for the embedded client cdylib is redirected
to a discarded sink in its build.rs so it doesn't leak `RunnerClient`/`load`
into consumer bindings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The inline cache-miss reason now names the env var(s) that changed —
`env 'NODE_ENV' changed` (or `envs 'A', 'B' changed` for several) instead
of a bare `envs changed`. Applies to both manually-configured `env`
changes and runner-reported tracked-env / tracked-env-glob changes, so the
two render consistently. The detailed summary already named the vars.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… mismatches

Prerun spawn-fingerprint changes and post-run tracked-env / env-glob
mismatches each carried their own added/removed/changed shapes
(SpawnFingerprintChange env variants, PostRunMismatch option pairs,
EnvGlobDiff). Replace all three with a shared EnvMismatch enum whose
Display impl is the single source of the user-facing wording.

EnvGlobDiff is gone: glob validation now reports the first differing
entry (sorted lockstep scan, like detect_globbed_input_change) instead
of accumulating a full diff that rendering then flattened anyway. This
also deletes the impossible (None, None) arm in the old
tracked_env_to_spawn_change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…apshot

The IPC Recorder's env map and PostRunFingerprint::validate both read the
live process env (std::env::vars_os / var_os). Tasks' envs come from the
plan; std::env::vars_os is only meant to bootstrap the session snapshot
(Session.envs) at init. Thread that snapshot through ExecutionContext /
execute_spawn into Recorder::new (now Arc-shared) and through
ExecutionCache::try_hit into validate, so getEnv/getEnvs and the
tracked-env lookup validation resolve against the same map the plan was
built from — and tests can inject envs via Session::init_with.

Document the rule in CLAUDE.md (Code Constraints → Environment Variables).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…the plan

A command-prefixed env (PREFIXED_ENV=x node tool.mjs) is part of the
spawn's env, but getEnv resolved against the session snapshot only and
answered (unset) — and recorded that wrong value into the post-run
fingerprint.

The plan now saves two env maps per spawn: all_envs (filtered, passed to
the child — unchanged) and full_envs, the planning context's envs overlaid
with the spawn's own additions. Nested expansions already thread prefix
envs into the context, so full_envs picks up enclosing runs' prefixes too
(taskA: PREFIXED_A=a vt run taskB → taskB's getEnv sees PREFIXED_A).
full_envs is serde-skipped: it mirrors the ambient env and is not part of
the plan's identity.

The Recorder and tracked-env validation both use full_envs, keeping record
and lookup symmetric per cache key (prefix envs are in the spawn
fingerprint, so a changed prefix is a different entry).

e2e: fetch_env_sees_command_prefix_env (was failing, now green) and
fetch_env_sees_intermediate_prefix_envs (nested prefix accumulation +
cache-hit validation).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant