Skip to content

fix(patcher.mjs): merge process.env into the linter spawn env #868

Open
linzhp wants to merge 1 commit into
aspect-build:mainfrom
linzhp:patcher_env
Open

fix(patcher.mjs): merge process.env into the linter spawn env #868
linzhp wants to merge 1 commit into
aspect-build:mainfrom
linzhp:patcher_env

Conversation

@linzhp

@linzhp linzhp commented May 15, 2026

Copy link
Copy Markdown
Contributor
const ret = childProcess.spawnSync(config.linter, config.args, {
  stdio: "inherit",
  cwd: sandbox,
  env: config.env || {},
});

env: config.env || {} replaces the child's environment entirely — it does not extend the patcher's own env. config.env is whatever the rule passed via
patch_cfg_env in patcher_action.bzl, which for clang_tidy_action is just:

{ "MSYS_ARG_CONV_EXCL": "*", "MSYS_NO_PATHCONV": "1" }

i.e. no PATH. The spawned linter therefore runs with an empty PATH.

Symptom

Any linter wrapper that relies on PATH to resolve a binary breaks. The most visible case is lint/clang_tidy_wrapper.bash, which uses mktemp, cat, eval,
etc., and starts with #!/usr/bin/env bash. With no PATH:

  • /usr/bin/env bash may fail to find bash.
  • Even if bash starts, out_file=$(mktemp) returns empty, the redirection target becomes empty, and the wrapper can't write its captured clang-tidy output.

In --fix / lint:fix=True mode the failure is silent: JS_BINARY__SILENT_ON_SUCCESS=1 swallows the wrapper's stderr, the patcher still runs the post-spawn
diff step (which finds no changes because clang-tidy never ran), and the output .patch file ends up empty (0 bytes). To the caller it looks like "no fixes
available."

Fix

Merge process.env so any PATH (and other env vars Bazel set for the action via --action_env, the default shell env, etc.) reach the linter, while still
letting config.env override individual keys:

env: Object.assign({}, process.env, config.env || {}),

This matches the behavior most callers expect — config.env is treated as additions/overrides on top of the inherited environment, not as a hermetic replacement.

Repro

Use clang_tidy aspect with --@aspect_rules_lint//lint:fix=True --output_groups=rules_lint_patch on any cc_library target. Before the fix, the produced
.AspectRulesLintClangTidy.patch file is 0 bytes and the wrapper's .out file is also empty. After the fix, the wrapper actually invokes clang-tidy --fix, the
sandbox source gets modified, and the patch contains a real unified diff.

A direct repro at the wrapper level: invoke patcher.sh <patch_cfg.json> from the execroot with JS_BINARY__LOG_DEBUG=1. Before the fix, the spawned linter's
stderr (when reachable at all) shows shell errors complaining about missing commands; after the fix, you see normal clang-tidy output.

Notes

  • Callers that rely on a hermetic env (none in-tree today) can still set config.env to whatever they need — Object.assign lets explicit keys win over inherited
    ones.
  • This is consistent with how JS_BINARY__* env vars are already expected to flow through process.env for the js_binary runtime.

Test Plan

  • Existing patcher tests pass.
  • clang_tidy aspect in --fix mode produces a non-empty patch on a target with normal C++ compile flags (any target whose macro defines or copts would
    otherwise fail the path-less wrapper).
  • config.env values still override conflicting keys in process.env (verified by a unit test or by setting an env key explicitly in patch_cfg_env).
  • No regression for linters that pass an empty config.env.

Changes are visible to end-users: no

  • Searched for relevant documentation and updated as needed: yes/no
  • Breaking change (forces users to change their own code or config): yes/no
  • Suggested release notes appear below: yes/no

@CLAassistant

CLAassistant commented May 15, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@aspect-workflows

aspect-workflows Bot commented May 15, 2026

Copy link
Copy Markdown

Bazel 7 (Test)

3 test targets passed

Targets
//format/test:custom_args_test [k8-fastbuild] 243ms
//format/test:ls-files_test [k8-fastbuild]    565ms
//tools/sarif:sarif_test [k8-fastbuild]       102ms

Bazel 8 (Test)

All tests were cache hits

3 tests (100.0%) were fully cached saving 969ms.


Bazel 9 (Test)

All tests were cache hits

3 tests (100.0%) were fully cached saving 2s.

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.

2 participants