Skip to content

support-bundle exits 4 where the oracle exits 0 on a first-run host, and four unregistered issue codes reach the wire behind a hand-declared gate #374

Description

@alpCaner

Found by adversarially verifying the #357 fix

feat/v06-batch. #357's core fix is confirmed sound — the exit-code invariant holds on every return path, ok:true beside an error-severity issue is closed everywhere (not just the measured path), the oracle's check names and order match on all five target/server shapes, inspect.context is key-identical including projectSelected and debuggerExtensions, and the parity test genuinely spawns both binaries against a failing host. These are the gaps around it.


1. MAJOR — the port exits 4 where the oracle exits 0, on a first-run customer host

python/tan/commands/support_bundle_cmd.py:648 feeds exit_code_for(checks) a list containing the Python-only-widened longPaths check (tan-cli#306), which is not one of the oracle's fail axes.

Measured — same project, real PATH, HOME at a fresh scratch dir (no global .gitconfig, the first-run-customer state long_paths_check's own docstring names), registry LongPathsEnabled = 1, git core.longpaths unset:

RUST rc=0 ok=True  exitCode=0 issues=[]
PY   rc=4 ok=False exitCode=4 issues=[support-bundle.bootstrapManifest:warning,
                                      support-bundle.longPaths:error]

Identical divergence at --target-kind zephyr-mcu --server jlink, zephyr-mcu --server openocd, baremetal-mcu --server pyocd, yocto-userspace --server gdbserver, and native-host. With a developer HOME carrying core.longpaths=true both sides return 0 — which is exactly why it is invisible on our machines and visible on a customer's.

#357's acceptance criterion is "matching the oracle". This fails where the oracle passes, on the one command a customer runs after something else has already broken.

Fix. Drop longPaths from this command's verdict input, or restrict tan-cli#306's fail arm to tan doctor.

2. MAJOR — the parity test structurally cannot see it

python/tests/parity/test_support_bundle_oracle_parity.py:116-117 pins the exit-code assertion on the single scenario where both sides are already 4 (sdkRoot fails on both). Re-running the file's own comparison with --sdk-root <stub> gives RUST 0 / PY 4.

GIT_LONGPATHS_WIDENED (:63) exempts longPaths' status, and the file's docstring asserts that is "NOT an exemption from the comparison this file exists for" — but the exempted check drives exit_code_for, i.e. the exact (rc, ok, exitCode) triple line 117 compares.

Fix. Add a second case with a resolvable SDK, where the oracle returns rc=0.

3. MAJOR — unregistered issue codes are on the wire and the gate cannot see them

python/tests/gates/test_every_issue_code_is_registered.py:919-946 hand-declares this command's value space instead of deriving it — _resolve_helper takes declared["suffixes"] verbatim (:1097-1101).

Measured live emits absent from contract/issue-codes.json (which carries 22 support-bundle.* entries, none of these): support-bundle.sdkRoot (error), support-bundle.jlinkBackend, support-bundle.openocdBackend, support-bundle.pyocdBackend (warning). Also reachable and unregistered: support-bundle.gdb, support-bundle.gdbserverBackend, support-bundle.noneBackend.

So py -3.12 -m pytest tests/gates/ -q reports 11 passed while tan support-bundle puts an unregistered error code on the wire.

Fix. Register them, and re-derive (or re-declare) the suffix set from _debug_doctor_report's check names so the gate cannot drift again.

4. MAJOR — a stale exemption whose subject this diff deleted

The same entry's comment still reads "checks there is doctor_cmd._collect(...)'s own output, reused verbatim (_doctor_section's docstring)". _doctor_section was deleted by this diff.

Fourteen declared suffixes are now unemittable by this command — sdk, hostPython, jlink, pythonFloor, sdkProvenance, setools, sevenZip, venvProvenance, west, westResolved, workspace, zephyrSdk, zephyrVersion, zephyrWorkspace — and their contract/issue-codes.json notes repeat the same dead _doctor_section claim.

It is not an xfail(strict=True), so nothing XPASSes; it just keeps passing. The strict-xfail path was checked too: python/tests/conformance/test_contract_envelopes.py:120's DELIBERATE_DIVERGENCE and NOT_PORTED carry no support-bundle entry (the 5 xfails are all debug-config-preview-*, tan-cli#138), and test_frozen_issue_codes.py:321-339 is scoped to status in ("frozen","reserved") while these rows are pre-consumer. Nothing anywhere catches the dead codes.

5. MINOR — an undeclared divergence in the check list

support_bundle_cmd.py:235-241's _HOST_CHECK_ORDER keeps bootstrapManifest, a key the oracle's bundle does not have. Measured with a resolved SDK whose metadata/bootstrap.json is unreadable: the port emits 10 checks, the oracle 9, with bootstrapManifest: warn inserted before hostPrerequisites.

The parity test's exact name-list assertion (:120) passes only because its scenario resolves no SDK. #357's acceptance says "no deliberate-divergence exemption"; this one is deliberate, documented in the module, and undeclared in the parity file whose docstring claims the check list is "compared element-for-element".

6. MINOR — detail and fix are never compared, and they diverge

test_support_bundle_oracle_parity.py:82's _statuses compares (name, status) only — so the text a maintainer actually reads in the attached bundle is uncompared. Same failing host, oracle vs port:

  • hostPrerequisites"Missing required tools: git -> winget install -e --id Git.Git ..." vs "missing from PATH: git, cmake, python, ninja (tan's built-in fallback list)."
  • longPaths — different headline plus a port-only fix.
  • codeLLDBExtension — the oracle writes U+2014 EM DASH (bytes \xe2\x80\x94) where support_bundle_cmd.py:258-259 writes --, so that "port of doctor.rs::extension_check's None arm" is not byte-verbatim.

(homePath's difference is the declared redaction policy — not a finding.)

7. MINOR — the unit tests pin a check list that cannot occur in production

python/tests/commands/test_support_bundle_command.py:433-450 pins a 6-name list. Every case monkeypatches doctor_cmd._collect with _clean_checks() (:63-85), which returns only sdk + hostPrerequisites, so _HOST_CHECK_ORDER harvests exactly one name.

It is a genuine guard against the substituted-checklist defect, but no unit test would notice bootstrapManifest, longPaths, zephyrSdkAvailableForHost or homePath entering or leaving the bundle — which is how findings 1 and 5 got through.

Fix. Add one case that runs the real _collect, or assert the 9-name oracle shape.

8. NIT — stale comment

test_support_bundle_command.py:23: "support-bundle is not yet registered in tan.cli.app (the orchestrator's to wire)". It is registered at python/tan/cli.py:34 and :105, and the parity test depends on that.


Counts

py -3.12 -m pytest tests/commands/test_support_bundle_command.py tests/parity/test_support_bundle_oracle_parity.py tests/gates/ tests/conformance/ -q re-run: 395 passed, 3 skipped, 5 xfailed (not the reported 375/3 — the delta is other agents' concurrent additions, green either way). The 3 skips are ALP_SDK_ROOT/ALP_SDK_HAND_PORT_ROOT staleness gates; the 5 xfails are the debug-config-preview-* #138 divergences.

Related: #357, #306, #138.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions