Skip to content

[Tech Debt] tools/e2e.sh breaks without uv on PATH, and the tools/ut.sh pip fallback passes a pytest log level that errors out #982

Description

@weiqingy

Search before asking

  • I searched in the issues and found nothing similar.

Description

Two independent defects in the build scripts, both found while working on #980. They are unrelated in mechanism but both sit in the no-uv / fallback paths that CI never exercises, which is why neither has surfaced before. Filing them together since one change can cover both.

1. tools/e2e.sh breaks without uv on PATH

tools/e2e.sh invokes uv by bare name seven times, at :48, :57, :69, :75, :84, :93 and :156, and it has no command -v uv guard anywhere. Its only command -v is for mktemp at :125. So on a machine where uv is not on PATH, every one of those fails with uv: command not found.

This is unlike the sibling scripts. tools/lint.sh:67 and tools/ut.sh:222 both guard their uv usage and fall back to pip, and tools/build.sh installs uv itself before using it. tools/e2e.sh does neither. It only delegates to tools/build.sh at :101 and :114, and both of those are conditional:

if [[ ! -d "e2e-test/target" ]]; then
  bash tools/build.sh
fi
...
if [[ ! -f "uv.lock" ]]; then
  bash tools/build.sh
fi

So once a build has happened, the delegation is skipped. tools/build.sh -j also never runs the Python half at all, so it never installs uv.

Worth noting what the fix is not: rewriting these to python3 -m uv looks like the obvious parallel to #979, but it would make things worse here. That form requires an importable uv module, which only exists if uv was pip installed. Someone using a standalone uv (the curl installer or homebrew) has a working uv on PATH and no module, so the rewrite would turn a working run into a failing one. #979 deliberately leaves this file alone for that reason.

A guard is the right shape: probe once, prefer the PATH binary, fall back to the interpreter form only when the module is actually importable, and fail with a clear message when neither is available. That also fixes the current failure mode, where the script reports a bare command not found rather than telling you what to install.

2. tools/ut.sh pip fallback passes a log level pytest rejects

tools/ut.sh:282, :285 and :294 pass -o log_cli_level=${LOG_LEVEL:-OFF}, while the uv branch at :240, :249 and :263 passes ${LOG_LEVEL:-CRITICAL}. OFF is not a valid Python logging level name, and pytest rejects it:

$ python3 -m pytest <dir> -o log_cli=true -o log_cli_level=OFF
ERROR: 'OFF' is not recognized as a logging level name for 'log_cli_level'. Please consider passing the logging level num instead.
$ echo $?
4

Exit 4 is a usage error, raised during startup before any test is collected. So whenever the pip fallback path is taken and LOG_LEVEL is unset, the Python test run fails immediately without running a single test. Reproduced against pytest==9.0.3, the version pinned in python/pyproject.toml. The same command with CRITICAL starts normally.

The two branches were presumably meant to behave the same way, so aligning the fallback on CRITICAL looks like the intended value.

Are you willing to submit a PR?

  • I'm working on this and will open a PR (2026-08-08).

Metadata

Metadata

Assignees

No one assigned

    Labels

    fixVersion/0.4.0priority/majorDefault priority of the PR or issue.tech debt[Issue Type] User-unaware issues, such as code refactor and infrastructure maintenance.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions