Skip to content

NO_COLOR is checked two different ways, and faultdecode's copy diverges from the oracle #288

Description

@alpCaner

Found by an audit of where the Python port hand-rolls what Typer/Click already provides. This one is a real behavioural divergence, not a style point.

Two hand-written copies, already drifted

python/tan/commands/faultdecode_cmd.py:91   if os.environ.get("NO_COLOR"):              # truthy check
python/tan/commands/size_cmd.py:404         os.environ.get("NO_COLOR") is not None      # presence check

The Rust oracle is presence-based — crates/tan-cli/src/style.rs:27 uses var_os("NO_COLOR").is_none() — which is also what the NO_COLOR spec requires: any value, including empty, disables colour.

So size_cmd matches the oracle and the spec. faultdecode_cmd does not: with NO_COLOR= (set but empty) it evaluates falsey and tan faultdecode keeps emitting colour, where tan size and the Rust binary both suppress it.

The obvious fix is a worse bug — flagging it explicitly

The instinct is typer.Option(..., envvar="NO_COLOR"). Do not. Click's bool-envvar coercion is truthy-string parsing (1 / true / yes / on …), not presence detection. NO_COLOR= would then raise not a valid boolean and crash the command — a regression neither hand-rolled copy has today, introduced by "using the framework properly".

The safe consolidation is a single shared presence-check helper, or a callback= — not the bare envvar= kwarg — matching style.rs.

Why this is worth more than the one-line fix

Two independent copies of one environment contract is the shape that produced the drift. Any third command that adds colour will copy whichever it finds first. One helper removes the class.

Related, same audit, no bug

faultdecode_cmd.py:130-152's _check_elf_path / _check_file_path are called manually at :221-222 rather than wired to their typer.Option() declarations, and their docstrings literally say "click.Path(exists=True, dir_okay=False) equivalent". Moving them to callback= is a pure relocation with identical typer.BadParameter text and param_hints — the precedent is two files over at new_som_cmd.py:553,569. Zero observable change, so no oracle-parity risk. Same for the ten _parse_hexint(...) call sites.

What the same audit found NOT worth changing, recorded so it is not re-proposed

  • add_completion=True does not close Deferred: seven commands are entirely unported (scaffold, completion, diff, pinmux, inspect, trace, support-bundle) #260. The oracle's tan completion --shell {bash,zsh,fish} (crates/tan-cli/src/commands/completion.rs) is a subcommand emitting hand-captured static scripts with a test suite proving every clap flag round-trips; Typer's is --install-completion/--show-completion root flags driving Click's dynamic protocol. Flipping the flag adds a second, non-parity mechanism, changes root --help observably, and needs its own --format json interception (both are eager Click options that print and exit before root()).
  • no_args_is_help=True would flip bare tan from exit 2 to exit 0 — a documented oracle-parity break.
  • rich_help_panel= grouping: clap's help is equally flat, so grouping only the Python port's --help would itself be the divergence.
  • typer.confirm in bootstrap's workspace guard: deliberately absent, and the code says why — "NO PROMPT, ever, in this port … the oracle hung forever on exactly that."
  • cli.py:437-541's _TeeStderr / SystemExit machinery is load-bearing, not a gap: it folds a pre-dispatch usage error into the stdout envelope while still streaming stderr live, and typer==0.27.0's Click-alike hierarchy is private (typer._click.*), so scraping rendered text is the version-stable seam.
  • ALP_FLASH_FORCE env-only with no --force flag mirrors crates/tan-cli/src/commands/flash/mod.rs:171 exactly — deliberate on both sides.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingoracle-parityDivergence from the released Rust oracle; measure by RUNNING the binarypython-portRust-to-Python port of the tan command surface

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions