From 28c256bbc96eeaaad450e2411d77a9d1adc9bbfd Mon Sep 17 00:00:00 2001 From: Likhan Siddiquee Date: Wed, 30 Sep 2026 16:26:46 +0000 Subject: [PATCH 1/3] chore(deps): update pre-commit hooks to latest majors Preserve strict commit-msg checks and prevent the weekly updater from reporting semver downgrades as stale hooks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/pre-commit-autoupdate.yml | 34 +++++++++++++++++++++ .pre-commit-config.yaml | 5 +-- docs/06-field-notes.md | 6 ++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pre-commit-autoupdate.yml b/.github/workflows/pre-commit-autoupdate.yml index 66955be..ddbe243 100644 --- a/.github/workflows/pre-commit-autoupdate.yml +++ b/.github/workflows/pre-commit-autoupdate.yml @@ -43,6 +43,40 @@ jobs: python -m pip install --upgrade pre-commit # Modifies the working copy only — never committed or pushed. pre-commit autoupdate + # autoupdate can select an older tag (e.g. gitleaks v8.30.0 over v8.30.1). + # Keep newer pinned semver releases rather than reporting a downgrade. + python - <<'PY' + import re + import subprocess + from pathlib import Path + + import yaml + + path = Path(".pre-commit-config.yaml") + original = yaml.safe_load( + subprocess.check_output(["git", "show", "HEAD:.pre-commit-config.yaml"], text=True) + ) + updated = yaml.safe_load(path.read_text()) + original_revs = { + item["repo"]: item["rev"] + for item in original["repos"] + if item["repo"] != "local" + } + text = path.read_text() + for item in updated["repos"]: + repo = item["repo"] + if repo not in original_revs: + continue + old_rev, new_rev = original_revs[repo], item["rev"] + old = re.fullmatch(r"v?(\d+)\.(\d+)\.(\d+)", old_rev) + new = re.fullmatch(r"v?(\d+)\.(\d+)\.(\d+)", new_rev) + if old and new and tuple(map(int, new.groups())) < tuple(map(int, old.groups())): + proposed = f" - repo: {repo}\n rev: {new_rev}\n" + if text.count(proposed) != 1: + raise ValueError(f"Cannot restore pinned revision for {repo}") + text = text.replace(proposed, f" - repo: {repo}\n rev: {old_rev}\n", 1) + path.write_text(text) + PY if git diff --quiet -- .pre-commit-config.yaml; then echo "pre-commit hooks are up to date." exit 0 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e5092bb..a734eea 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,7 +28,7 @@ repos: # Repo hygiene — fast, language-agnostic sanity checks. # --------------------------------------------------------------------------- - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: trailing-whitespace # Markdown trailing whitespace is meaningful (two spaces = hard break). @@ -88,9 +88,10 @@ repos: # Conventional Commits — enforced on the commit message (commit-msg hook). # --------------------------------------------------------------------------- - repo: https://github.com/compilerla/conventional-pre-commit - rev: v3.6.0 + rev: v4.4.0 hooks: - id: conventional-pre-commit + args: ["--strict"] stages: [commit-msg] # --------------------------------------------------------------------------- diff --git a/docs/06-field-notes.md b/docs/06-field-notes.md index 5de4255..070577a 100644 --- a/docs/06-field-notes.md +++ b/docs/06-field-notes.md @@ -448,6 +448,12 @@ Base: `~/.vscode-server/data/User/` Pre-commit-**only** hosted hooks (gitleaks, markdownlint-cli2, conventional-pre-commit, pre-commit-hooks) have a single pin so they don't drift, but Dependabot can't bump them either — they only move via `pre-commit autoupdate`. +- **Hook major update, 2026-09-30:** `pre-commit-hooks` v6 requires Python >=3.9 (CI uses 3.12); + `conventional-pre-commit` v4 permits merge/fixup messages by default, so pass `--strict` to + preserve the previous rejection behavior. `pre-commit autoupdate` can also select a _lower_ + tag: it proposed gitleaks v8.30.0 while v8.30.1 was pinned. The weekly staleness workflow + restores semver downgrades before deciding whether to open an issue; retain the newer pin and + test both valid and rejected commit messages when changing the commit-msg hook. - **Edit-tool unicode trap.** The string-replace edit tools can write `\uXXXX` escapes as **literal text**. Use the actual glyphs (em-dash —, middot ·, arrow →, section §) in the replacement, or a Python heredoc with ASCII anchors for unicode-heavy edits. From 17d8a249e9a70a5739b375e1628305ad045345ed Mon Sep 17 00:00:00 2001 From: Likhan Siddiquee Date: Wed, 30 Sep 2026 17:04:58 +0000 Subject: [PATCH 2/3] fix(ci): detect hook releases across side branches Compare stable GitHub tags rather than git-describe on upstream HEAD, which misses the gitleaks 8.30.1 release branch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/pre-commit-autoupdate.yml | 63 +++++++++++---------- docs/06-field-notes.md | 9 +-- 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/.github/workflows/pre-commit-autoupdate.yml b/.github/workflows/pre-commit-autoupdate.yml index ddbe243..59a0280 100644 --- a/.github/workflows/pre-commit-autoupdate.yml +++ b/.github/workflows/pre-commit-autoupdate.yml @@ -7,7 +7,7 @@ # ZERO-CODE-SYNC by design: this workflow NEVER pushes repo content. It has no # `contents: write`, opens no branch, and runs no `git push`. On a stale hook it # only opens/updates a tracking ISSUE (a text notification); a maintainer then -# runs `pre-commit autoupdate` locally and commits. This deliberately avoids the +# reviews the tagged releases and updates pins locally. This deliberately avoids the # common "auto-open a PR" pattern, which would require a push path in the repo. # # NOTE: ruff is intentionally NOT covered here — it is single-sourced via the @@ -40,42 +40,43 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | - python -m pip install --upgrade pre-commit - # Modifies the working copy only — never committed or pushed. - pre-commit autoupdate - # autoupdate can select an older tag (e.g. gitleaks v8.30.0 over v8.30.1). - # Keep newer pinned semver releases rather than reporting a downgrade. + # autoupdate uses git describe on the default branch; release tags on + # side branches (e.g. gitleaks v8.30.1) can be missed or downgraded. python - <<'PY' import re import subprocess from pathlib import Path - import yaml - path = Path(".pre-commit-config.yaml") - original = yaml.safe_load( - subprocess.check_output(["git", "show", "HEAD:.pre-commit-config.yaml"], text=True) - ) - updated = yaml.safe_load(path.read_text()) - original_revs = { - item["repo"]: item["rev"] - for item in original["repos"] - if item["repo"] != "local" - } text = path.read_text() - for item in updated["repos"]: - repo = item["repo"] - if repo not in original_revs: - continue - old_rev, new_rev = original_revs[repo], item["rev"] - old = re.fullmatch(r"v?(\d+)\.(\d+)\.(\d+)", old_rev) - new = re.fullmatch(r"v?(\d+)\.(\d+)\.(\d+)", new_rev) - if old and new and tuple(map(int, new.groups())) < tuple(map(int, old.groups())): - proposed = f" - repo: {repo}\n rev: {new_rev}\n" - if text.count(proposed) != 1: - raise ValueError(f"Cannot restore pinned revision for {repo}") - text = text.replace(proposed, f" - repo: {repo}\n rev: {old_rev}\n", 1) - path.write_text(text) + hosted = re.findall( + r"(?m)^ - repo: https://github.com/([\w.-]+/[\w.-]+)\n rev: (v?\d+\.\d+\.\d+)$", + text, + ) + if len(hosted) != text.count(" - repo: https://github.com/"): + raise ValueError("A hosted pre-commit hook has an unrecognized revision") + for repo, pinned in hosted: + tags = subprocess.check_output( + ["gh", "api", "--paginate", f"repos/{repo}/tags?per_page=100", "--jq", ".[].name"], + text=True, + ).splitlines() + stable = [ + (tuple(map(int, match.groups())), tag) + for tag in tags + if (match := re.fullmatch(r"v?(\d+)\.(\d+)\.(\d+)", tag)) + ] + if not stable: + raise ValueError(f"No stable version tags found for {repo}") + version, latest = max(stable) + if version > tuple(map(int, pinned.lstrip("v").split("."))): + old = f" - repo: https://github.com/{repo}\n rev: {pinned}\n" + if text.count(old) != 1: + raise ValueError(f"Cannot update pinned revision for {repo}") + text = text.replace( + old, f" - repo: https://github.com/{repo}\n rev: {latest}\n", 1 + ) + if text != path.read_text(): + path.write_text(text) PY if git diff --quiet -- .pre-commit-config.yaml; then echo "pre-commit hooks are up to date." @@ -85,7 +86,7 @@ jobs: echo "Stale hooks detected:" echo "$diff" title="chore: pinned pre-commit hooks are out of date" - body="$(printf 'The weekly check found newer pinned revisions for pre-commit hooks that Dependabot cannot bump (it has no `pre-commit` ecosystem).\n\nRun `pre-commit autoupdate` locally, review, and commit the result.\n\n```diff\n%s\n```\n' "$diff")" + body="$(printf 'The weekly check found newer pinned revisions for pre-commit hooks that Dependabot cannot bump (it has no `pre-commit` ecosystem).\n\nReview the release tags and update the pins locally (autoupdate may miss tags on side branches).\n\n```diff\n%s\n```\n' "$diff")" existing="$(gh issue list --state open --search "$title in:title" --json number --jq '.[0].number')" if [ -n "$existing" ]; then gh issue comment "$existing" --body "$body" diff --git a/docs/06-field-notes.md b/docs/06-field-notes.md index 070577a..3508165 100644 --- a/docs/06-field-notes.md +++ b/docs/06-field-notes.md @@ -450,10 +450,11 @@ Base: `~/.vscode-server/data/User/` they only move via `pre-commit autoupdate`. - **Hook major update, 2026-09-30:** `pre-commit-hooks` v6 requires Python >=3.9 (CI uses 3.12); `conventional-pre-commit` v4 permits merge/fixup messages by default, so pass `--strict` to - preserve the previous rejection behavior. `pre-commit autoupdate` can also select a _lower_ - tag: it proposed gitleaks v8.30.0 while v8.30.1 was pinned. The weekly staleness workflow - restores semver downgrades before deciding whether to open an issue; retain the newer pin and - test both valid and rejected commit messages when changing the commit-msg hook. + preserve the previous rejection behavior. `pre-commit autoupdate` uses `git describe` on upstream HEAD, not the highest version tag. + Gitleaks v8.30.1 is on a release commit diverged from upstream master, so autoupdate proposes + v8.30.0 instead. The weekly staleness workflow now checks all stable GitHub tags by version, + including side-branch releases; retain the newer pin and test both valid and rejected commit + messages when changing the commit-msg hook. - **Edit-tool unicode trap.** The string-replace edit tools can write `\uXXXX` escapes as **literal text**. Use the actual glyphs (em-dash —, middot ·, arrow →, section §) in the replacement, or a Python heredoc with ASCII anchors for unicode-heavy edits. From e15ef712e147afc76401c6c3e6c3ebd8e4039e4c Mon Sep 17 00:00:00 2001 From: Likhan Siddiquee Date: Wed, 30 Sep 2026 17:10:14 +0000 Subject: [PATCH 3/3] chore: keep hook update scoped to version pins Restore the existing weekly checker; document the upstream autoupdate tag-selection caveat for manual review. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/pre-commit-autoupdate.yml | 45 +++------------------ docs/06-field-notes.md | 8 ++-- 2 files changed, 8 insertions(+), 45 deletions(-) diff --git a/.github/workflows/pre-commit-autoupdate.yml b/.github/workflows/pre-commit-autoupdate.yml index 59a0280..66955be 100644 --- a/.github/workflows/pre-commit-autoupdate.yml +++ b/.github/workflows/pre-commit-autoupdate.yml @@ -7,7 +7,7 @@ # ZERO-CODE-SYNC by design: this workflow NEVER pushes repo content. It has no # `contents: write`, opens no branch, and runs no `git push`. On a stale hook it # only opens/updates a tracking ISSUE (a text notification); a maintainer then -# reviews the tagged releases and updates pins locally. This deliberately avoids the +# runs `pre-commit autoupdate` locally and commits. This deliberately avoids the # common "auto-open a PR" pattern, which would require a push path in the repo. # # NOTE: ruff is intentionally NOT covered here — it is single-sourced via the @@ -40,44 +40,9 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | - # autoupdate uses git describe on the default branch; release tags on - # side branches (e.g. gitleaks v8.30.1) can be missed or downgraded. - python - <<'PY' - import re - import subprocess - from pathlib import Path - - path = Path(".pre-commit-config.yaml") - text = path.read_text() - hosted = re.findall( - r"(?m)^ - repo: https://github.com/([\w.-]+/[\w.-]+)\n rev: (v?\d+\.\d+\.\d+)$", - text, - ) - if len(hosted) != text.count(" - repo: https://github.com/"): - raise ValueError("A hosted pre-commit hook has an unrecognized revision") - for repo, pinned in hosted: - tags = subprocess.check_output( - ["gh", "api", "--paginate", f"repos/{repo}/tags?per_page=100", "--jq", ".[].name"], - text=True, - ).splitlines() - stable = [ - (tuple(map(int, match.groups())), tag) - for tag in tags - if (match := re.fullmatch(r"v?(\d+)\.(\d+)\.(\d+)", tag)) - ] - if not stable: - raise ValueError(f"No stable version tags found for {repo}") - version, latest = max(stable) - if version > tuple(map(int, pinned.lstrip("v").split("."))): - old = f" - repo: https://github.com/{repo}\n rev: {pinned}\n" - if text.count(old) != 1: - raise ValueError(f"Cannot update pinned revision for {repo}") - text = text.replace( - old, f" - repo: https://github.com/{repo}\n rev: {latest}\n", 1 - ) - if text != path.read_text(): - path.write_text(text) - PY + python -m pip install --upgrade pre-commit + # Modifies the working copy only — never committed or pushed. + pre-commit autoupdate if git diff --quiet -- .pre-commit-config.yaml; then echo "pre-commit hooks are up to date." exit 0 @@ -86,7 +51,7 @@ jobs: echo "Stale hooks detected:" echo "$diff" title="chore: pinned pre-commit hooks are out of date" - body="$(printf 'The weekly check found newer pinned revisions for pre-commit hooks that Dependabot cannot bump (it has no `pre-commit` ecosystem).\n\nReview the release tags and update the pins locally (autoupdate may miss tags on side branches).\n\n```diff\n%s\n```\n' "$diff")" + body="$(printf 'The weekly check found newer pinned revisions for pre-commit hooks that Dependabot cannot bump (it has no `pre-commit` ecosystem).\n\nRun `pre-commit autoupdate` locally, review, and commit the result.\n\n```diff\n%s\n```\n' "$diff")" existing="$(gh issue list --state open --search "$title in:title" --json number --jq '.[0].number')" if [ -n "$existing" ]; then gh issue comment "$existing" --body "$body" diff --git a/docs/06-field-notes.md b/docs/06-field-notes.md index 3508165..7ef792b 100644 --- a/docs/06-field-notes.md +++ b/docs/06-field-notes.md @@ -450,11 +450,9 @@ Base: `~/.vscode-server/data/User/` they only move via `pre-commit autoupdate`. - **Hook major update, 2026-09-30:** `pre-commit-hooks` v6 requires Python >=3.9 (CI uses 3.12); `conventional-pre-commit` v4 permits merge/fixup messages by default, so pass `--strict` to - preserve the previous rejection behavior. `pre-commit autoupdate` uses `git describe` on upstream HEAD, not the highest version tag. - Gitleaks v8.30.1 is on a release commit diverged from upstream master, so autoupdate proposes - v8.30.0 instead. The weekly staleness workflow now checks all stable GitHub tags by version, - including side-branch releases; retain the newer pin and test both valid and rejected commit - messages when changing the commit-msg hook. + preserve the previous rejection behavior. Test both valid and rejected commit messages. + `pre-commit autoupdate` uses `git describe` on upstream HEAD rather than the highest tag; + gitleaks v8.30.1 is tagged off master, so review its proposed v8.30.0 downgrade manually. - **Edit-tool unicode trap.** The string-replace edit tools can write `\uXXXX` escapes as **literal text**. Use the actual glyphs (em-dash —, middot ·, arrow →, section §) in the replacement, or a Python heredoc with ASCII anchors for unicode-heavy edits.