Skip to content

No gate catches a residual argh import: priv shipped a zero-help-diff migration with a live CommandError regression in it #41

Description

@thorwhalen

Not a bug in cw's grammar — a gap in what cw gives a migrator to check their work with. Filing it because it just bit the fleet's flagship migration.

What happened

thorwhalen/priv#120 migrated a 101-command CLI to cw and shipped a committed characterization golden proving zero --help diff across 105 vectors. By every gate cw offers, that migration was complete. It was not: priv/git_branch_tool.py still had

try:
    from argh import CommandError
except ImportError:
    CommandError = ValueError

— twice, verbatim — and priv/git_ops.py still ended in a live argh.dispatch_commands under a __main__ guard that python -m priv.git_ops reaches today.

The CommandError one was not cosmetic. It is the exact trap cw/compat.py warns about in prose ("One trap the shim structurally cannot fix… Grep for it"). Because argh.CommandError is cw.CommandError → False, all five raise sites escaped cw.run as unhandled tracebacks where argh had printed CommandError: <msg> / exit 1. A user-visible regression, live on main for the whole interval between #120 and thorwhalen/priv#122.

Why nothing caught it

Each existing gate is blind to it by construction, and that is the point:

  • cw.testing goldens characterize --help and parser-reject vectors. A CommandError fires after dispatch, past everything a --help corpus can reach. priv's corpus additionally must never actually run a command (they mutate the local ecosystem), so it structurally cannot cover this.
  • python -m cw specs / help work on one function's signature. They never see a module-level import.
  • cw.compat aliases CommandError correctly, but only helps a module that switched to from cw import compat as argh. A module that imported the name from real argh is untouched.
  • Declared-dependency checks don't fire either: priv's pyproject.toml correctly listed only ["cw…", "python-dotenv"], so the imports were undeclared and worked purely because argh happened to be installed on the dev machine. On a clean install the module would have silently fallen back to ValueError.

So the failure mode is: the grammar is provably identical, the metadata is provably clean, and the migration is still incomplete. The prose warning in cw/compat.py is the only defence, and prose does not run in CI.

Suggested shape

A python -m cw audit <path> (name TBD) that greps a migrated tree and reports, with line numbers:

  1. import argh / from argh import … anywhere — the headline check. Special-case from argh import CommandError with the class-identity explanation, since it is the one that silently changes runtime behaviour rather than merely failing to import.
  2. argh.dispatch* / ArghParser call sites still reachable — including under if __name__ == "__main__", which is easy to skim past precisely because it looks dead. (priv's was reachable: python -m priv.git_ops --help prints a 22-command parser.)
  3. argh in install_requires / dependencies with no corresponding import — the LGPL-exposure-only case. i2mint/oui_notebook was exactly this and no wave caught it until Remove the argh dependency declaration, which nothing imports oui_notebook#2.
  4. Optionally: a cw.dispatch(...) call whose result is discarded. cw returns the exit code where argh exited itself, so cw.dispatch(funcs) without raise SystemExit(...) silently turns every non-zero exit into 0 — another difference no --help golden can see.

Checks 1-3 are pure grep and could ship as a doctest-able function plus a __main__ subcommand; check 4 wants ast. Even just 1 and 2, runnable in a repo's CI after migration, would have turned both of today's residuals into a red build instead of a six-week silence.

Related: #29 (deletion sweep), #30 (ledger).

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions