You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
ci(test): lint compiled bash bodies with shellcheck (#496)
* ci(test): lint compiled bash bodies with shellcheck
Adds tests/bash_lint_tests.rs, an integration test that compiles a
representative set of fixtures and runs shellcheck against every
literal bash: body in the generated YAML. The lint catches the actual
silent-failure patterns ADO's "fail on last command" default lets
through (SC2164 cd-without-||, SC2155 masked-return, SC2086/2046
unquoted variables, SC2154 unset refs, SC2088 tilde-in-quotes).
This replaces the previously proposed approach of sprinkling
`set -eo pipefail` across every bash step (PR #492). That approach
added boilerplate to ~27 sites without enforcement, drifted as new
steps were added, and in two spots actually masked errors more than
the original code (`grep ... | tail -1 || true`).
Real bugs surfaced and fixed by the new lint:
* `src/engine.rs` — `Engine::Copilot::log_dir()` returned
`~/.copilot/logs`. Tilde does not expand inside the double-quoted
`[ -d "..." ]` test that consumes this value, so the directory check
always failed and Copilot logs were silently never collected to the
pipeline artifact. Replaced with `$HOME/.copilot/logs`.
* `src/runtimes/node/mod.rs` and `src/runtimes/dotnet/mod.rs` — the
ensure-`.npmrc` and ensure-`nuget.config` step generators used Rust
`\<newline>` line continuations in their format strings, which strip
leading whitespace. The emitted YAML had body lines flush-left
against `- bash: |`, producing invalid YAML. Replaced with raw
string literals so indentation is preserved.
* Multiple `cd "$DOWNLOAD_DIR"` in `base.yml` / `1es-base.yml` had no
`|| exit` guard. Added.
* `exit $AGENT_EXIT_CODE` (multiple sites) — quoted.
* `mkdir -p {{ working_directory }}/safe_outputs` and the matching
`cp -a ...` — quoted the substitution.
* `JSON_CONTENT=$(echo "$RESULT_LINE" | sed 's/.*PFX://')` rewritten
to `${RESULT_LINE##*PFX:}` (avoids forking sed and removes a
shellcheck SC2001 finding).
Targeted `set -eo pipefail` additions (only where masked-pipeline
exit codes matter):
* `base.yml` / `1es-base.yml` ado-aw download steps (3 stages × 2
templates): `grep "ado-aw-linux-x64" checksums.txt | sha256sum -c -`
silently passes when grep matches nothing because sha256sum returns
0 on empty stdin. Without pipefail, the unverified binary would
install successfully.
* `src/compile/extensions/trigger_filters.rs` script-download step:
same `grep | sha256sum` pattern.
* `src/runtimes/lean/mod.rs` install step: `curl ... | sh` would
silently install nothing on curl failure.
The two pre-existing `set -eo pipefail` instances on the AWF download
+ docker pull steps (introduced in PR #439) and on the `tee`-piped
agent / threat-analysis runs are preserved — those were correct.
Skip vs. enforce:
* Locally, the test prints a notice and returns early when shellcheck
is missing.
* CI installs shellcheck and sets `ENFORCE_BASH_LINT=1` so a missing
shellcheck becomes a hard failure rather than a silent skip.
A new `tests/fixtures/runtime-coverage-agent.md` exercises the Lean,
Node-with-feed-url, and .NET-with-feed-url runtimes plus the
cache-memory tool, ensuring every code-generated bash step is reached.
The lint enforces a `REQUIRED_STEP_DISPLAY_NAMES` coverage list to
catch fixture/generator drift.
Documented in AGENTS.md and docs/extending.md.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(bash-lint): close 1ES coverage gap and fix shellcheck 0.9 SC2002
Two changes the CI surfaced after PR #496 landed locally:
1. **shellcheck 0.9.0 (Ubuntu's pinned) flags SC2002 ("Useless cat")
on `cat file | sed ...` patterns that 0.11.0 does not.** Fixed by
rewriting the two offending sites in the MCPG start step:
* `MCPG_CONFIG=$(cat … | sed | sed | sed)` →
`MCPG_CONFIG=$(sed -e … -e … -e … file)`. Semantically equivalent
because the three substitutions are over independent placeholder
patterns.
* `cat … | python3 -m json.tool` → `python3 -m json.tool < …`.
Avoids forking `cat` for nothing and is stable across shellcheck
versions.
2. **Add a `runtime-coverage-1es-agent.md` fixture and assert that
every known compile target is exercised by at least one fixture.**
Previously only `1es-test-agent.md` compiled to the 1ES target, and
it had no `runtimes:` or `tools.cache-memory`. The code-generated
bash bodies from those extensions (Lean install, `.npmrc`,
`nuget.config`, cache-memory restore/init) were being linted only
on the standalone target. Today their bodies are byte-identical
across targets, but a future target-specific divergence would slip
past the lint without a 1ES variant.
`compile_fixture()` now parses `Generated <target> pipeline:` from
stdout, accumulates targets seen, and the test asserts every entry
in `REQUIRED_TARGETS = ["standalone", "1es"]` is covered. Sanity-
checked that removing the 1ES fixtures causes the test to fail with
`no fixture compiles to the following target(s): ["1es"]`.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
0 commit comments