Skip to content

feat(plugin-exec): deterministic default tool set instead of blind /usr/bin PATH - #297

Open
raphaelvigee wants to merge 1 commit into
masterfrom
raphaelvigee/fix-pluginexec-deterministic-toolchain
Open

feat(plugin-exec): deterministic default tool set instead of blind /usr/bin PATH#297
raphaelvigee wants to merge 1 commit into
masterfrom
raphaelvigee/fix-pluginexec-deterministic-toolchain

Conversation

@raphaelvigee

Copy link
Copy Markdown
Member

Summary

  • exec/bash/sh no longer fall back to a hardcoded PATH=/usr/local/bin:/usr/bin:/bin when .hephconfig doesn't set path — that blindly trusted whatever happened to be installed in those 3 directories, differing across hosts/distros/CI images (even though PATH was never part of the target's cache key).
  • New default_tools driver option: a curated list of common POSIX tools (sh, bash, cat, grep, sed, tar, sleep, ...), each turned into an implicit, hashed //@heph/bin:<name> tool dependency — reusing the existing hostbin/tool_group_inputs/bin_dir-symlink machinery an explicit target-level tools = [...] dependency already goes through, rather than a bespoke PATH-directory trust.
  • Inclusion is gated by a fixed directory probe (/usr/local/bin:/usr/bin:/bin), not heph's own ambient $PATH — so the curated set is identical across shell setups on a given host. The concrete binary is still resolved (and hashed into the target's input hash) for real by the existing hostbin driver's own which at run time.
  • default_tools and the existing raw-directory path escape hatch are mutually exclusive. A user-configured default_tools list errors loudly on a name that can't be probed (a statement, not best-effort); the built-in curated default silently skips a missing name (an implicit default must never fail a target that never calls it).
  • Deliberately scoped to Driver::from_options_exec/bash/sh (the real .hephconfig-driven entry point, where hostbin is already registered in bootstrap.rs) — the bare new_exec/new_bash/new_sh constructors and the interactive --shell fallback keep the old ambient-PATH fallback, since ~40 existing test call sites across the engine/e2e suites don't register hostbin.
  • EXEC_DEF_FORMAT_VERSION bumped 1→2 (a new hashed field, default_tool_inputs, was added to the per-target def hash — invalidates every exec/bash/sh cache entry deliberately, not by coincidence).

Process

This went through the standing review board before and after implementation, per this repo's CLAUDE.md:

  • Design (product-vision, feature-quality, hermeticity, in parallel): converged on routing default tools through the existing //@heph/bin:<name> + tool_group_inputs pipeline instead of a bespoke which+symlink-directory mechanism — reuses tested, already-hashed infra and closes the cache-correctness gap for free.
  • Review (code-quality, after implementation): found one real BLOCKER (sleep missing from the curated list broke crates/bin-e2e/tests/tui_pty.rs and shell_pty.rs, plus the example/ workspaces) and several MAJOR/MINOR findings, all fixed — see commit body and diff for detail (staging annotations for cheaper per-sandbox cost, explicit-vs-curated error semantics, dedup, a more accurate spawn-failure diagnostic, etc).

One design fork came up mid-review that this repo's own conventions say isn't an agent's call to make alone (per-platform/per-environment behavior): default tools are resolved via hostbin's which, which reads heph's own invoking process's $PATH, not a fixed list — so (a) env -i heph run ... (empty/minimal PATH — CI, systemd, sandboxed hosts) breaks every exec/bash/sh target where the old hardcoded PATH always worked, and (b) two devs on the same host/arch with different shell setups can get a different concrete binary for the same curated name. Raised this directly; the call was to keep ambient-$PATH resolution via hostbin as-is and document it as a known limitation rather than build a more invasive fixed-directory resolver for this PR. See the doc comment on DEFAULT_TOOLS_PROBE_PATH in crates/plugin-exec/src/pluginexec/mod.rs.

Test plan

  • cargo test -p plugin-exec — 99 unit tests, including new coverage for config decode/validation (mutual exclusion, dedup, explicit-vs-curated probe error semantics, invalid tool names), parse() wiring (hashed //@heph/bin:<name> inputs, hash changes on default_tools change), and the existing suite unaffected.
  • cargo test -p e2e --test pluginexec_default_tools — new e2e test file against a real Engine with hostbin registered: default tool resolves without an explicit tools= dep, a tool outside the list fails clearly, an overlapping explicit tools= dep doesn't conflict with a same-named default, and the actual shipped curated list (no config override) runs something real.
  • cargo clippy --all-targets -- -D warnings / cargo fmt --check clean on touched crates.
  • Full local e2e (release bin-e2e suite, all test binaries) green after the sleep fix — confirmed ctrl_c_interrupts_foreground_child_not_the_session and tui_renders_the_run_and_restores_the_terminal (the two tests the missing-sleep BLOCKER broke) both pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_0173dXh8yqPq5FYV6bBuXRnY

…sr/bin PATH

The exec/bash/sh drivers fell back to a hardcoded
PATH=/usr/local/bin:/usr/bin:/bin whenever .hephconfig didn't set `path` —
exposing whatever happened to be installed there, differing across
hosts/distros/CI images even though the target's cache key never saw it.

Replace the fallback with a curated `default_tools` list, each name turned
into an implicit, hashed //@heph/bin:<name> tool dependency — the same
tested/hashed/deduped machinery an explicit `tools = [...]` target
declaration already goes through, rather than a bespoke PATH-directory
trust. Inclusion is gated by a fixed-directory probe (not heph's own
ambient $PATH), so the curated *set* is identical across shell setups on a
given host; the concrete binary is still resolved (and hashed) for real by
the existing `hostbin` driver at run time.

`default_tools` is mutually exclusive with the existing raw-directory
`path` escape hatch. An explicitly configured list errors loudly on a name
that can't be probed (a user statement, not best-effort); the built-in
curated default silently skips a missing name (an implicit default must
never fail a target that never calls it). Scoped to
Driver::from_options_exec/bash/sh (the .hephconfig-driven entry point,
where `hostbin` is already registered) — the bare new_exec/new_bash/new_sh
constructors and the interactive --shell fallback keep the old ambient
fallback, since ~40 existing test call sites don't register `hostbin`.

Went through product-vision/feature-quality/hermeticity design review
before implementation, and a code-quality pass on the diff after. Keeping
ambient-$PATH resolution via hostbin (rather than a fixed-dir resolver for
the actual binary, only for the inclusion gate) was an explicit call by
the repo owner, accepting env -i / minimal-PATH hosts and per-shell-setup
binary divergence as a known, documented limitation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0173dXh8yqPq5FYV6bBuXRnY
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