Repository navigation
fix(setup-preflight): read the version floor through each harness's own listing - #1673
Conversation
c36ec02 to
ac258ac
Compare
…wn listing read_installed only ever ran 'claude plugin list --json', so on Codex, Gemini CLI or Copilot CLI the floor was never checked: the missing binary collapsed the listing to unknown and _floor_findings said nothing. Probe each harness's own listing command in order and let the first binary that exists answer for the machine; a listing it cannot read stays unknown. Refs apache#1641.
ac258ac to
831d20f
Compare
Kaap10
left a comment
There was a problem hiding this comment.
Thanks @liwenjie200543 for working on #1641! Broadening setup-preflight version floor detection across other harnesses is an important improvement.
There are a few functional blockers with the current implementation that prevent it from working in live environments, plus a couple of edge cases to address:
1. Major: Missing JSON flags for gemini and codex listing commands
In plugins/magpie-setup/skills/setup/setup_preflight/cli.py:
HARNESS_LISTINGS: tuple[tuple[str, list[str]], ...] = (
("claude-code", ["claude", "plugin", "list", "--json"]),
("codex", ["codex", "plugin", "list"]),
("gemini", ["gemini", "extensions", "list"]),
)- Gemini CLI: The default output format for
gemini extensions listis human-readable text (--output-formatchoices:["text", "json"], default:"text"). Without-o json(or--output-format json), it prints plain text (e.g.No extensions installed.or text lines). - Codex CLI:
codex plugin listalso prints a formatted terminal summary by default. It requires--json(codex plugin list --json) to emit a JSON array.
Impact: In actual environments, completed.stdout contains plain text. _parse_listing catches json.JSONDecodeError and returns None (unknown). As a result, the floor check continues to be silently skipped on live Codex and Gemini runs. The unit tests in test_cli.py only passed because the mock returned JSON for a command that does not emit JSON in reality.
2. Major: Sequential PATH probing vs detecting the active running harness
In plugins/magpie-setup/skills/setup/setup_preflight/cli.py:
for _harness, argv in HARNESS_LISTINGS:
...- Issue #1641 specifically directed: "Detect the running harness, and read its installed versions with its own listing command".
- When multiple AI CLIs are installed on the same machine (e.g.
claudeinstalled globally via npm/brew, but the user is currently working inside a Gemini CLI or Codex session), the sequential loop findsclaudeonPATHfirst and queries it. If Claude doesn't have the Magpie plugin installed, it returnsNoneand halts, completely ignoring the running Gemini or Codex session. - Magpie already has canonical harness-detection patterns (see
tools/adversarial-review/src/adversarial_review/detect.py) using ambient environment variables:CLAUDECODE == "1"➔ Claude CodeCODEX_SANDBOXorCODEX_THREAD_ID➔ CodexGEMINI_CLI == "1"orGEMINI_PROJECT_DIRin env ➔ Gemini CLICOPILOT_CLI➔ Copilot CLI
The active running harness should be detected first; sequential PATH probing should only be an ambiguous fallback if no active harness environment is detected.
3. Major: Missing preflight-floor eval suite coverage
Issue #1641 asked to:
"Add preflight-floor cases for a below-floor Codex and Gemini install."
tools/skill-evals/evals/setup/preflight-floor/fixtures/case-5-no-claude-cli/report.md currently records the previous behavior where Gemini 0.2.0 was ignored. Updating this fixture or adding dedicated eval cases (and validating with python3 tools/skill-evals/render-preflight-fixtures.py --check) is needed to protect against future regressions.
4. Minor / Nits:
- Copilot CLI: Mentioned in the title and description of #1641 ("setup-preflight: the version floor is never checked on Codex, Gemini CLI or Copilot CLI"), but omitted from
HARNESS_LISTINGS. - Unit test mock fidelity (
test_cli.py): Intest_a_missing_claude_binary_falls_through_to_codex, the mock should assert that--json/-o jsonis present inargvso realistic CLI arguments are verified. - CLI help string in
build_parser():Now that listing is harness-agnostic, the help text should say:parser.add_argument( "--plugin-list", metavar="PATH", help="`claude plugin list --json` output to use instead of running it ('-' for stdin)", )
"harness plugin listing JSON to use instead of running it ('-' for stdin)".
potiuk
left a comment
There was a problem hiding this comment.
Thanks for picking up #1641 — the shared _parse_listing and the tests are a good base, but the probe can answer from the wrong harness, and the Codex and Gemini listings will most likely never parse, so the floor stays unchecked where the issue needs it.
Why an unreadable result is unknown rather than absent. (plugins/magpie-setup/skills/setup/setup_preflight/cli.py:111)
Why an unreadable result is unknown rather than absent. Inside a
sandboxed session the plugin cache is read-denied andclaude plugin list --jsonreturns[]there — that reads exactly like "nothing installed"
but is not. Acting on it would propose installing a project's entire floor
on every sandboxed run.
except (OSError, subprocess.SubprocessError):
# The binary is not on PATH: this is not the running harness,
# so the next harness's own command decides.
continue
The handler treats every OSError and SubprocessError as "binary not on PATH", but that set also covers a claude that exists and times out (TimeoutExpired) or that the sandbox refuses to execute (PermissionError). Both then fall through to the next harness, which contradicts the rule the PR states itself ("the first binary that exists decides the answer") and borrows another harness's listing for an unreadable one. Checked against the PR head: with claude raising TimeoutExpired or PermissionError and codex returning a listing, read_installed(None) returns the Codex map after probing ['claude', 'codex'].
Suggested fix: Fall through only on FileNotFoundError; any other OSError or SubprocessError from a binary that exists returns None. Add a test for each (TimeoutExpired, PermissionError) asserting None and that codex is never probed.
The actions. (plugins/magpie-setup/skills/setup/setup_preflight/cli.py:56)
The actions.
- a floor plugin absent →
claude plugin install <plugin>@apache-magpie;- a floor plugin below
min_version→claude plugin update <plugin>@apache-magpie.Where there is no such CLI, run nothing and print the commands instead.
HARNESS_LISTINGS: tuple[tuple[str, list[str]], ...] = (
("claude-code", ["claude", "plugin", "list", "--json"]),
("codex", ["codex", "plugin", "list"]),
("gemini", ["gemini", "extensions", "list"]),
)
The first binary on PATH is not the running harness. Many maintainers have several harnesses installed, and the floor is then checked against whichever comes first in this tuple. This is not hypothetical: in the sandboxed Claude Code session this review ran in, claude is not on PATH while codex and gemini are, so the Codex listing would answer for a Claude Code session. The finding that follows also proposes the claude plugin install/update commands from step-3, whichever harness's listing produced it, and the _harness id is discarded, so nothing downstream can tell which install was measured. Issue #1641 asks to "detect the running harness, and read its installed versions with its own listing command".
Suggested fix: Detect the running harness first (the signals in install.md Step M2, for example the harness's own environment variables), run only that harness's listing, and return the harness id with the map so the findings name that harness's commands. When the harness cannot be detected, stay unknown rather than probing in a fixed order.
Smaller observations
- See inline comments on
plugins/magpie-setup/skills/setup/setup_preflight/cli.py:58,tools/setup-preflight/tests/test_cli.py:147.
This review was drafted by an AI-assisted tool and
confirmed by an Apache Magpie maintainer. After you've
addressed the points above and pushed an update, an Apache Magpie
maintainer — a real person — will take the next look
at the PR. The findings cite the project's review criteria;
if you think one of them is mis-applied, please reply on the
PR and a maintainer will weigh in.More on how Apache Magpie handles maintainer review:
Contributing guide.
…ting Review feedback on apache#1673: a fixed PATH probe could answer from the wrong harness, and the Codex and Gemini listings never parsed without their JSON flags, so the floor stayed unchecked where the issue needs it. Detect the running harness from its own environment markers (the canonical innermost-first order from tools/adversarial-review), run only that harness's listing (`codex plugin list --json`, `gemini extensions list -o json`), and return the harness id with the map so the below-floor finding names that harness's install and update commands (step-3.md now carries the per-harness table). A harness with no listing CLI (Copilot) and a listing that cannot be produced - missing, timed out, refused, non-zero, unparsable - all stay unknown; nothing is probed in a fixed order. Tests cover each marker, the innermost-first order, the JSON flags, every unknown path, and the harness named in the finding and the cache key; the preflight-floor eval cases gain the below-floor Codex and Gemini installs issue apache#1641 asked for (case-5 updated, case-9 added).
|
Pushed a rework addressing both reviews:
Verification: |
Upstream apache#1669 added the case-9 first-magpie-run and case-10 utilities-first-run eval fixtures; our added below-floor Codex case becomes case-11 to keep the numbering unique. Everything else merges cleanly: the harness-detection rework and the upstream welcome-guide machinery touch different regions of the same files.
…ework prek on the merge flagged three follow-ups: case-11 lacked the state.json the renderer requires, the eval-case counts lagged the new case, and two files needed `ruff format`. The renderer gains a `harness` state key so a case whose prose names a harness renders the verdict that harness actually gets: the renderer puts that harness's session markers in the environment and a stub listing command on PATH that prints the case's listing, so the checker detects the harness itself instead of reading the always-unknown `--plugin-list` shape. Every floor case now names its harness; the five below-floor reports re-render with `facts.harness` set.
Narrow `state["harness"]` to str before using it as a PATH stub name and a marker-table key, per workspace mypy.
CodeQL flags a world-readable chmod mask; the renderer runs the stub as itself, so 0o700 keeps the exec bit without the extra reach.
potiuk
left a comment
There was a problem hiding this comment.
Thanks for this round. Every code point from my two earlier reviews is addressed:
- the running harness is detected from its own markers;
- a missing, timed-out or refused binary stays unknown and never falls through to another harness's answer;
- Gemini's single
magpieextension is compared against every floor entry and reported asoutdated, nevermissing, with the parametrised above-floor CLI test; - Codex reads only the
installedarray, backed by a real 0.149.1 fixture, and needs an exact floor name before its listing is believed; - the Codex update command has one spelling in every doc and in the eval;
- case-5 is the print-path eval again, and the Gemini cases are new cases.
What was left was small, so rather than send it back I pushed a fixup commit (d503ae16) to your branch:
setup_preflight/cli.py: the--plugin-listpath now detects the running harness too, so a supplied Gemini listing keeps the single-extension rule and a supplied Codex envelope is unwrapped. Before, it always gotharness=None, and a Geminimagpieentry read as every floor plugin missing. Two new tests intests/test_cli.pycover it.sections/step-3.md: one paragraph saying what to print whenfacts.harnessisnull, which until now the evals could only infer from a case's prose. The pre-flight eval reports were re-rendered to match.case-17-below-floor-geminiandcase-18-at-floor-geminigot the samelocal-smoke/smoketags as the Codex case, so the Gemini cases run in smoke runs.
I also trimmed the labels to what the change implements; most of the 24 came in from main through the merge commits.
One note for whoever lands #1724, #1725 or #1726 next: they add case-16-* fixtures and edit the same case-count lines, so they will need a rebase and renumbering.
Thanks!
This review was drafted by an AI-assisted tool and
confirmed by an Apache Magpie maintainer. The maintainer
approving this PR has read the findings and signed off. If
something feels off, please reply on the PR and a maintainer
will follow up.More on how Apache Magpie handles maintainer review:
Contributing guide.
- Renumber the first-run eval case to case-19, after the case-16..18 floor cases that landed with apache#1673. - Say that audit-finding-fix asks before every write and commit, not only before the draft PR. - Give the license audit its own prompt in the flow and drop the sentence that repeated it. - Name what a "make it yours" request becomes, as the other family guides do. Generated-by: Claude Opus 5
* feat(setup): add repo-health family first-run welcome guide * Apply review fixups for the repo-health welcome guide - Renumber the first-run eval case to case-19, after the case-16..18 floor cases that landed with #1673. - Say that audit-finding-fix asks before every write and commit, not only before the draft PR. - Give the license audit its own prompt in the flow and drop the sentence that repeated it. - Name what a "make it yours" request becomes, as the other family guides do. Generated-by: Claude Opus 5 --------- Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
- Renumber the first-run eval case to case-20, after the floor cases from apache#1673 and the repo-health case from apache#1724. - Present isolated-setup-update as the read-only drift check it is, and stop reusing the section heading's emoji for that step. - Name what a "make it yours" request becomes, as the other family guides do. - One sentence per line where the guide wrapped mid-sentence. Generated-by: Claude Opus 5
* feat(setup): add setup family first-run welcome guide * Apply review fixups for the setup welcome guide - Renumber the first-run eval case to case-20, after the floor cases from #1673 and the repo-health case from #1724. - Present isolated-setup-update as the read-only drift check it is, and stop reusing the section heading's emoji for that step. - Name what a "make it yours" request becomes, as the other family guides do. - One sentence per line where the guide wrapped mid-sentence. Generated-by: Claude Opus 5 --------- Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
- Renumber the first-run eval case to case-21, after the floor cases from apache#1673 and the repo-health and setup cases. - List release-trains.md under "Configure it": prepare, step 1 of the guide's own flow, stops without it. - Say where the distribution, approval and announcement backends are set. - Use concrete tailoring requests in "Make it yours". Generated-by: Claude Opus 5
…1726) * feat(setup): add release-management family first-run welcome guide * Apply review fixups for the release-management welcome guide - Renumber the first-run eval case to case-21, after the floor cases from #1673 and the repo-health and setup cases. - List release-trains.md under "Configure it": prepare, step 1 of the guide's own flow, stops without it. - Say where the distribution, approval and announcement backends are set. - Use concrete tailoring requests in "Make it yours". Generated-by: Claude Opus 5 --------- Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
What
The setup preflight now detects the active harness from its environment markers and reads only that harness's installed versions. Missing, failed, empty or malformed listings stay unknown rather than becoming evidence that the whole project floor is absent.
Codex reads the
installedarray in itsinstalled/availableJSON envelope. A redacted live Codex 0.149.1 format fixture is included; the same envelope was also checked with the desktop-bundled 0.163 CLI. Non-Claude listings need an exact floor-plugin name (Codex) or the exactmagpieextension name (Gemini) before they are trusted. Gemini's single extension version is compared against every family in the floor, so an older extension reportsoutdated, while an extension at or above the floor does not report missing families. Gemini uses the exact-name safeguard pending a live-format fixture.The Codex refresh command is consistently
codex plugin marketplace upgrade apache-magpie, verified with CLI help and the official plugin documentation. The missing-CLI print path has its own eval again, alongside separate Codex/Gemini cases. Main's five newer welcome cases are retained, with 18 preflight-floor cases in total. The no-CLI case supplies a captured listing through--plugin-listand a reconciliation stamp, isolating printing from unrelated setup proposals. The unadopted fixture explicitly requests adoption information while only permitting personal configuration. The fixture output contract explicitly excludes welcome-recording writes from installation commands.How checked
prek run --all-fileson Ubuntu 24.04.5: 40 passed, 0 failed, including workspace Ruff, Mypy, pytest, link checks, the rendered-fixture drift guard and token stamps.AI assistance: this update was prepared and validated with Codex; the independent behavioral evaluation used GPT-5.6 Terra.