Skip to content

regen-ci-req workflows report drift permanently: lockfile is not reproducible across checkouts #229

Description

@speediedan

🐛 Bug

The generated CI lockfile is not byte-reproducible across checkouts, so regen-ci-req-report.yml and regen-ci-req-check.yml should be reporting drift on every run regardless of whether any pin actually changed.

Expected symptoms: a regen-check-pins comment on every PR that touches the relevant paths, and a [auto] regen pinned requirements PR every two weeks (00:00 UTC on the 1st and 15th) whose diff is only the lockfile header and/or a trailing newline.

Found while porting these workflows to finetuning-scheduler, which had the same defects. Both are fixed there; this issue is to carry the fixes back.

Cause 1 — absolute --output-file path baked into the uv header

uv pip compile records its --output-file argument verbatim in the generated header. generate_lockfile() passes an absolute path (requirements/utils/lock_ci_requirements.sh:165, where output_file derives from CI_DIR at :35), so the committed lockfile contains this machine's checkout location:

# requirements/ci/requirements.txt:2
#    uv pip compile pyproject.toml --extra examples --extra lightning --group dev --group test \
#      --group profiling --output-file /home/speediedan/repos/interpretune/requirements/ci/requirements.txt ...

On a GitHub runner that path becomes /home/runner/work/interpretune/interpretune/..., so git diff is non-empty even when every resolved pin is identical.

This is a regression introduced by the pyproject.toml migration — the older requirements.in-based invocation used relative paths, which is why 5ad0f82 was clean at the time.

Fix: generate_lockfile() already does pushd "${REPO_ROOT}" at :152, so the path can simply be made repo-relative before building the command:

local rel_output_file="${output_file#${REPO_ROOT}/}"
# ... then use "${rel_output_file}" in the compile_cmd array instead of "${output_file}"

Keep using the absolute ${output_file} for prune_torch_only_deps and the echo messages.

Cause 2 — trailing-newline mismatch

uv pip compile emits the lockfile with no trailing newline. The end-of-file-fixer hook (.pre-commit-config.yaml:28) adds one on commit. The committed requirements/ci/requirements.txt does end with 0a, so every regeneration produces a one-byte diff against it.

Fix: normalize at the end of generate_lockfile():

if [[ -s "${output_file}" && -n "$(tail -c 1 "${output_file}")" ]]; then
    printf '\n' >> "${output_file}"
fi

Verification

After both fixes, regenerating against the committed state should produce no diff:

./requirements/utils/lock_ci_requirements.sh
git --no-pager diff --exit-code requirements/ci/requirements.txt   # expect exit 0

Worth also running it twice consecutively to confirm idempotency.

Note on diff scope

Separately: regen-ci-req-report.yml:65 and regen-ci-req-check.yml:24 set compare_paths to requirements/ci/requirements.txt only, while the lock script also rewrites requirements-oldest.txt and torch-override.txt. Drift in those goes undetected on the report path — but create-pull-request in the scheduled workflow will still commit them. Widening compare_paths (and/or adding add-paths to the create-pull-request step) would make the two consistent.

Environment

  • interpretune @ eb54af1
  • Affects: .github/workflows/regen-ci-req-report.yml, .github/workflows/regen-ci-req-check.yml, requirements/utils/lock_ci_requirements.sh

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions