🐛 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
🐛 Bug
The generated CI lockfile is not byte-reproducible across checkouts, so
regen-ci-req-report.ymlandregen-ci-req-check.ymlshould be reporting drift on every run regardless of whether any pin actually changed.Expected symptoms: a
regen-check-pinscomment on every PR that touches the relevant paths, and a[auto] regen pinned requirementsPR 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-filepath baked into the uv headeruv pip compilerecords its--output-fileargument verbatim in the generated header.generate_lockfile()passes an absolute path (requirements/utils/lock_ci_requirements.sh:165, whereoutput_filederives fromCI_DIRat:35), so the committed lockfile contains this machine's checkout location:On a GitHub runner that path becomes
/home/runner/work/interpretune/interpretune/..., sogit diffis non-empty even when every resolved pin is identical.This is a regression introduced by the
pyproject.tomlmigration — the olderrequirements.in-based invocation used relative paths, which is why 5ad0f82 was clean at the time.Fix:
generate_lockfile()already doespushd "${REPO_ROOT}"at:152, so the path can simply be made repo-relative before building the command:Keep using the absolute
${output_file}forprune_torch_only_depsand the echo messages.Cause 2 — trailing-newline mismatch
uv pip compileemits the lockfile with no trailing newline. Theend-of-file-fixerhook (.pre-commit-config.yaml:28) adds one on commit. The committedrequirements/ci/requirements.txtdoes end with0a, so every regeneration produces a one-byte diff against it.Fix: normalize at the end of
generate_lockfile():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 0Worth also running it twice consecutively to confirm idempotency.
Note on diff scope
Separately:
regen-ci-req-report.yml:65andregen-ci-req-check.yml:24setcompare_pathstorequirements/ci/requirements.txtonly, while the lock script also rewritesrequirements-oldest.txtandtorch-override.txt. Drift in those goes undetected on the report path — butcreate-pull-requestin the scheduled workflow will still commit them. Wideningcompare_paths(and/or addingadd-pathsto thecreate-pull-requeststep) would make the two consistent.Environment
.github/workflows/regen-ci-req-report.yml,.github/workflows/regen-ci-req-check.yml,requirements/utils/lock_ci_requirements.sh