Skip to content

Harden the repository and npm releases against Shai-Hulud - #16

Open
natanelia wants to merge 34 commits into
mainfrom
agent/harden-shai-hulud-supply-chain
Open

Harden the repository and npm releases against Shai-Hulud#16
natanelia wants to merge 34 commits into
mainfrom
agent/harden-shai-hulud-supply-chain

Conversation

@natanelia

@natanelia natanelia commented Aug 5, 2026

Copy link
Copy Markdown
Owner

What changed

This adds defense in depth against self-propagating npm supply-chain attacks such as Shai-Hulud.

Dependency execution

  • Disable Bun and npm lifecycle scripts globally and explicitly on every CI install.
  • Remove dependency script trust from both package.json and bun.lock.
  • Reject repository-owned install/publish lifecycle hooks, non-registry dependency specifications, and non-registry lockfile sources.
  • Add Dependabot coverage for text bun.lock files and GitHub Actions, with a seven-day cooldown for ordinary version updates while security updates remain immediate.
  • Add a dependency-review check for every pull request.

GitHub Actions

  • Pin every action to an immutable 40-character commit SHA.
  • Enforce an exact allowlist of audited Action repositories and commit SHAs.
  • Disable persisted checkout credentials in every job.
  • Add a dependency-free supply-chain policy check that fails on floating or unapproved actions, lifecycle hooks, URL/git dependencies, reusable npm credentials, unsafe installs, lockfile trust, or weakened workflow permissions.
  • Permit repository-write access only in the final GitHub Release job; package-manager code never runs with repository-write credentials.
  • Replace the third-party Pages publisher and repository-write token with GitHub's official artifact/Pages deployment flow. Build dependencies run with read-only repository permission; only the isolated deploy job receives pages: write and OIDC.
  • Add joint CODEOWNERS protection for workflows, lockfiles, manifests, release scripts, and security policy through @natanelia and @Joezer-Ivan.

Versioning without write credentials

  • Run Changesets only in a manual, read-only workflow.
  • Validate that generated changes are limited to Changeset deletion, package versions, the React package's internal core range, changelogs, and the lockfile.
  • Emit a checksummed version-packages.patch artifact that a maintainer must inspect, apply to a branch, and submit as a normal reviewed pull request.
  • Exercise the exact same semantic validator in ordinary PR CI.

npm release integrity

  • Remove the reusable NPM_TOKEN path and direct token-based release script.
  • Add a manual Stage npm release workflow using npm OIDC trusted publishing and npm stage publish only.
  • Build and verify packages in a read-only job with no npm credential or OIDC permission.
  • Abort release preparation if verification mutates tracked source files.
  • Pack each tarball exactly once with lifecycle scripts disabled, reject unexpected or sensitive paths, record the complete file manifest plus npm integrity/SHA-1/SHA-256, and pass those immutable tarballs to the isolated staging job.
  • Require npm's independent 2FA-backed human approval before either package can become public.
  • Add a separate Finalize npm release workflow, restricted to main, which creates v<version> only after both npm packages are public and their source manifests match the exact staged commit.

Operations

  • Add SECURITY.md with required GitHub rulesets, protected environments, security features, npm trusted-publisher settings, the release-review procedure, and incident response.
  • Track the administrative work that repository code cannot perform in Complete post-merge Shai-Hulud security configuration #17, assigned to @natanelia and @Joezer-Ivan.

Why

Shai-Hulud propagated through package lifecycle scripts, stole long-lived npm and GitHub credentials, modified repositories and workflows, and published further compromised package versions. The previous repository still retained dependency script trust, used a reusable npm credential, persisted checkout credentials, followed moving Action tags, granted a third-party Pages action repository-write access, and left main unprotected.

The new design assumes even a build dependency can execute malicious code. Such code receives neither repository-write nor npm-publishing authority. CI can submit only an immutable staged tarball, and npm requires a separate maintainer inspection plus 2FA before public release.

Validation

The final GitHub Actions run passes all ten jobs:

  • supply-chain policy and Node/npm staged-publishing toolchain
  • dependency review
  • formatting and linting
  • TypeScript
  • unit tests and coverage
  • dependency audit
  • semantic Changesets/version-patch validation
  • builds, performance budgets, packed ESM/CommonJS consumers, and package metadata
  • minimum Node.js 18 consumers
  • real worker tests in Chromium, Firefox, and WebKit

The release artifact packer was additionally exercised against representative package builds and produced two tarballs, a complete file manifest, npm integrity values, and valid SHA-256 checksums.

Required post-merge configuration

Repository code cannot self-grant administrative controls. Complete issue #17 before treating releases as protected:

  1. Protect main with PR-only merges, independent Code Owner review, stale-review dismissal, resolved conversations, all CI checks, no force pushes/deletion, and enforcement for administrators.
  2. Protect v* tags.
  3. Require independent approval on the npm-release and github-release environments.
  4. Enable repository security features and keep default Actions permissions read-only.
  5. For both npm packages, configure trusted publishing for stage-release.yml + npm-release, allow staged publishing only, require WebAuthn 2FA and disallow tokens, then revoke NPM_TOKEN and every other package-write token.

Summary by CodeRabbit

  • Security

    • Strengthened supply-chain protections, release validation, credential handling, and workflow permissions.
    • Added documented procedures for reporting vulnerabilities and responding to suspected compromises.
  • Release Management

    • Introduced staged release preparation, artifact verification, approval, and finalization workflows.
    • Added checksums and manifest validation to improve release integrity and traceability.
  • Documentation

    • Updated development and release instructions to reflect the new verification and publishing process.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds repository-wide supply-chain controls and replaces automatic releases with manually triggered version preparation, verified artifact staging, and release finalization workflows.

Changes

Release and supply-chain hardening

Layer / File(s) Summary
Supply-chain policy foundation
.github/CODEOWNERS, .github/dependabot.yml, .npmrc, bunfig.toml, package.json, SECURITY.md, README.md, .gitignore
Adds ownership, dependency update, registry, install-script, package, artifact, and security-policy configuration.
Policy validation and hardened CI
scripts/check-supply-chain-security.mjs, .github/workflows/ci.yml, .github/workflows/deploy-playground.yml
Validates repository and workflow policy. CI and playground jobs pin actions, disable checkout credentials, restrict permissions, and install dependencies without lifecycle scripts.
Version patch preparation
scripts/validate-version-changes.mjs, .github/workflows/release.yml
Validates generated package version changes and creates a checksummed patch artifact instead of publishing automatically.
Release artifact staging
scripts/prepare-release-artifacts.mjs, .github/workflows/stage-release.yml
Validates package metadata and tarball contents, writes checksums and a release manifest, then verifies and stages packages through trusted npm publishing.
Release finalization
.github/workflows/finalize-release.yml
Validates the release commit, package versions, and tag target before creating or reusing the GitHub release.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Maintainer
  participant VersionWorkflow
  participant StageWorkflow
  participant NpmRegistry
  participant FinalizeWorkflow
  participant GitHubRelease
  Maintainer->>VersionWorkflow: prepare and validate version patch
  VersionWorkflow-->>Maintainer: upload checksummed patch artifact
  Maintainer->>StageWorkflow: provide release version
  StageWorkflow->>NpmRegistry: stage verified package tarballs
  Maintainer->>FinalizeWorkflow: provide version and commit
  FinalizeWorkflow->>NpmRegistry: verify published package versions
  FinalizeWorkflow->>GitHubRelease: create or reuse release tag
Loading

Possibly related issues

  • Issue 17: The PR implements the release-hardening and supply-chain controls described by the issue.

Suggested reviewers: joezer-ivan

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the pull request's primary goal of hardening the repository and npm release process against supply-chain attacks.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/harden-shai-hulud-supply-chain

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@natanelia
natanelia requested a review from Joezer-Ivan August 5, 2026 04:46

Copy link
Copy Markdown
Owner Author

Final validation is complete: CI run #190 passed all ten jobs, including the supply-chain policy, dependency review, audit, semantic version-patch validation, package builds/consumers, and Chromium/Firefox/WebKit real-worker tests.

The repository-side hardening is complete on this branch. Administrative controls that code cannot self-grant are tracked in #17 and must be completed before the new release path is treated as protected. Joezer's independent review remains requested.

@natanelia
natanelia marked this pull request as ready for review August 5, 2026 16:13

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
scripts/prepare-release-artifacts.mjs (1)

164-183: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Assert built output and publishable dependency ranges.

ALLOWED_PACKAGE_FILES permits package.json/README-only packages, so a missing or stale dist/ build can pass the metadata check. Also validate that packed dependencies do not retain workspace: ranges, because publishing a package with packages/comlink-worker-pool-react/package.json containing workspace:* for comlink-worker-pool makes consumers fail to resolve the dependency.

Add both checks after assertPackageContents(packageInfo.name, packResult.files).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/prepare-release-artifacts.mjs` around lines 164 - 183, After
assertPackageContents in the packing flow, validate that the packed files
include the expected built dist/ output and reject packages missing or
containing stale build artifacts. Also inspect packed package metadata and
reject any dependency range using the workspace: protocol, including
dependencies such as comlink-worker-pool; keep both validations before
constructing the packedPackages entry.
scripts/validate-version-changes.mjs (1)

69-83: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Strip Changesets-updated dependency fields.

stripAllowedChanges ignores peerDependencies, devDependencies, and optionalDependencies, but Changesets versioning can update ranges in those fields too. Make this stripping use the Changesets schema/order so it remains valid if packages/comlink-worker-pool-react/package.json declares comlink-worker-pool outside dependencies; a hard-coded index === 1 check ties the allowlist to PACKAGE_PATHS order.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/validate-version-changes.mjs` around lines 69 - 83, Update
stripAllowedChanges to remove comlink-worker-pool from every dependency field
Changesets can update—dependencies, peerDependencies, devDependencies, and
optionalDependencies—using the Changesets schema/order rather than assuming
dependencies only. Replace the index === 1 package check in the PACKAGE_PATHS
mapping with a package-identity or path-based determination so the allowlist
remains correct if PACKAGE_PATHS order changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/stage-release.yml:
- Around line 86-91: Update the release setup around “Set up release-compatible
Node.js” to install/pin npm 11.15.0 or newer before staging. Adjust the staging
flow so npm stage publish runs from a temporary checkout or working directory
containing the package, rather than passing a tarball path, and preserve the
existing release behavior.

In `@scripts/validate-version-changes.mjs`:
- Around line 27-34: Update scripts/validate-version-changes.mjs at lines 27-34
to stage all files with intent-to-add before enumerating changes, then use git
diff HEAD --name-status --no-renames and allow status A for changelog paths.
Update .github/workflows/release.yml at lines 44-46 to run git add --all before
generating the version patch and use git diff --cached --binary --full-index,
ensuring validation and patch generation inspect the same staged set.
- Line 29: Update the git diff argument list containing “--find-renames=0” to
use “--no-renames” instead, ensuring rename detection is disabled and renamed
files are reported as separate additions and deletions.

---

Nitpick comments:
In `@scripts/prepare-release-artifacts.mjs`:
- Around line 164-183: After assertPackageContents in the packing flow, validate
that the packed files include the expected built dist/ output and reject
packages missing or containing stale build artifacts. Also inspect packed
package metadata and reject any dependency range using the workspace: protocol,
including dependencies such as comlink-worker-pool; keep both validations before
constructing the packedPackages entry.

In `@scripts/validate-version-changes.mjs`:
- Around line 69-83: Update stripAllowedChanges to remove comlink-worker-pool
from every dependency field Changesets can update—dependencies,
peerDependencies, devDependencies, and optionalDependencies—using the Changesets
schema/order rather than assuming dependencies only. Replace the index === 1
package check in the PACKAGE_PATHS mapping with a package-identity or path-based
determination so the allowlist remains correct if PACKAGE_PATHS order changes.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ccb83922-e22e-4188-92c3-336059a9b0da

📥 Commits

Reviewing files that changed from the base of the PR and between 3561b3f and 1b98898.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • .github/CODEOWNERS
  • .github/dependabot.yml
  • .github/workflows/ci.yml
  • .github/workflows/deploy-playground.yml
  • .github/workflows/finalize-release.yml
  • .github/workflows/release.yml
  • .github/workflows/stage-release.yml
  • .gitignore
  • .npmrc
  • README.md
  • SECURITY.md
  • bunfig.toml
  • package.json
  • scripts/check-supply-chain-security.mjs
  • scripts/prepare-release-artifacts.mjs
  • scripts/release.mjs
  • scripts/validate-version-changes.mjs
💤 Files with no reviewable changes (1)
  • scripts/release.mjs

Comment on lines +86 to +91
- name: Set up release-compatible Node.js
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: 24
package-manager-cache: false
registry-url: https://registry.npmjs.org/

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

npm stage publish tarball path argument supported npm docs

💡 Result:

The npm publish command natively supports providing a tarball path as an argument [1][2]. The command follows the syntax: npm publish [1][3]. A package-spec can be interpreted as a gzipped tarball, or a folder containing a package.json file [1][3]. If you are providing a local path (either to a folder or a tarball), the documentation notes that if it is a relative path, it should begin with an explicit./ prefix [1]. In summary, you can publish a package directly from a tarball file by passing its path to the npm publish command [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
sed -n '70,175p' .github/workflows/stage-release.yml

echo
echo "== checkout references in workflow =="
rg -n "checkout|setup-node|npm stage|npm install|artifact|tarball|package" .github/workflows/stage-release.yml

echo
echo "== repository files =="
git ls-files | sed -n '1,120p'

echo
echo "== package manifests =="
for f in $(git ls-files | rg '(^|/)package\.json$'); do
  echo "--- $f"
  python3 - <<'PY' "$f"
import json, sys
p=sys.argv[1]
with open(p) as fh:
    data=json.load(fh)
for k in ("name","private","publishConfig","packageManager","scripts"):
    if k in data:
        print(f"{k}={data[k]!r}")
PY
done

Repository: natanelia/comlink-worker-pool

Length of output: 13756


🌐 Web query:

npm stage command added npm 11.15.0

💡 Result:

The npm stage command was officially added in npm version 11.15.0, which was released on May 20, 2026 [1][2]. This command introduced staged publishing, a workflow that allows package maintainers to decouple the act of publishing from the requirement of two-factor authentication (2FA) [3][4]. With npm stage publish, an automated CI/CD workflow can stage a package version without a 2FA prompt [5][6]. The package is held in a pending state and is not publicly available until a maintainer explicitly approves it, a step that does require 2FA [7][4][8]. The npm stage command includes several subcommands for managing this process [5][6]: npm stage publish: Stages a package for publishing (no 2FA required). npm stage list: Lists all staged package versions. npm stage view: Views details of a specific staged package. npm stage approve: Approves and publishes a staged package (requires 2FA). npm stage reject: Rejects and removes a staged package (requires 2FA). npm stage download: Downloads the staged tarball for local inspection.

Citations:


🌐 Web query:

Node.js 24 bundled npm version

💡 Result:

Node.js 24 ships with npm version 11 [1][2]. Specifically, the initial release of Node.js 24 (v24.0.0) included npm 11.0.0 [1][2]. Throughout the lifecycle of the Node.js 24 release line, the bundled npm version may be updated in subsequent releases [2].

Citations:


🌐 Web query:

npm stage stage:publish help --stage staging tarball argument

💡 Result:

The command npm stage publish is part of npm's staged publishing feature, which allows maintainers to upload a package to a staging area for later approval, rather than publishing it directly [1][2]. This workflow is designed to improve security by allowing automated CI/CD systems to stage a package without requiring 2FA, while deferring the 2FA requirement to the manual approval step [3][4]. There is no specific tarball argument for the npm stage publish command [1][5]. The command automatically packs the current working directory as a tarball and uploads it to the npm registry [1][4]. Key details regarding this command and process: * Purpose: npm stage publish creates a pending version of your package that is not yet publicly available [1][2]. * 2FA Requirement: The staging step does not require 2FA [1][6]. 2FA is only required during the subsequent npm stage approve step [2][6]. * Inspection: If you need to inspect the contents of a staged package, you can use the npm stage download <stage-id> command to download the tarball for local review [1][3]. * System Requirements: Staged publishing is available in npm CLI version 11.15.0 or later [2][4]. If you are working programmatically with libnpmpublish, the underlying library uses an opts.stage option (set to true) to perform this action, handling the manifest and tarball data as inputs [7]. For standard CLI usage, you simply navigate to your package directory and run npm stage publish [2][6].

Citations:


🌐 Web query:

Node.js 24 current npm version in August 2026

💡 Result:

As of August 5, 2026, the current npm version included with Node.js 24.19.0 is npm 11.17.0 [1][2]. Node.js 24.19.0 was released on August 3, 2026 [3][1].

Citations:


Upgrade npm before staging and stage from the package directory.

npm stage is unavailable in the older Node 24 npm versions, and npm stage publish stages the current package directory rather than accepting a tarball path. Pin/install npm 11.15.0 or newer and create a temp checkout pointing at the package dir, or otherwise stage from the correct package directory before calling npm stage publish.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/stage-release.yml around lines 86 - 91, Update the release
setup around “Set up release-compatible Node.js” to install/pin npm 11.15.0 or
newer before staging. Adjust the staging flow so npm stage publish runs from a
temporary checkout or working directory containing the package, rather than
passing a tarball path, and preserve the existing release behavior.

Comment on lines +27 to +34
const changedLines = execFileSync(
"git",
["diff", "--name-status", "--find-renames=0"],
{ encoding: "utf8" },
)
.trim()
.split(/\r?\n/)
.filter(Boolean);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

The version-patch flow uses a diff basis that excludes untracked and staged changes. Both the validator and the patch generator read git diff without a commit argument. Files that bun run version creates are untracked, so the allowlist never inspects them and the patch never contains them. A reviewer can therefore apply a version patch that is missing a generated changelog while every checksum verifies.

  • scripts/validate-version-changes.mjs#L27-L34: enumerate changes with git diff HEAD --name-status --no-renames after git add --all --intent-to-add, and accept status A for the changelog paths only.
  • .github/workflows/release.yml#L44-L46: run git add --all before the patch step and build the patch with git diff --cached --binary --full-index, so the validated set and the patched set are identical.
📍 Affects 2 files
  • scripts/validate-version-changes.mjs#L27-L34 (this comment)
  • .github/workflows/release.yml#L44-L46
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/validate-version-changes.mjs` around lines 27 - 34, Update
scripts/validate-version-changes.mjs at lines 27-34 to stage all files with
intent-to-add before enumerating changes, then use git diff HEAD --name-status
--no-renames and allow status A for changelog paths. Update
.github/workflows/release.yml at lines 44-46 to run git add --all before
generating the version patch and use git diff --cached --binary --full-index,
ensuring validation and patch generation inspect the same staged set.


const changedLines = execFileSync(
"git",
["diff", "--name-status", "--find-renames=0"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

git diff --find-renames=0 versus --no-renames documentation

💡 Result:

In Git, --no-renames and --find-renames=0 are effectively equivalent in practice, as both disable rename detection, though they function slightly differently at the command-line level [1][2][3]. --no-renames This is the explicit flag to turn off rename detection [1][2]. It overrides any configuration (such as diff.renames=true) that might otherwise enable rename detection by default [1][3]. --find-renames=0 This uses the --find-renames (or -M) option with a similarity threshold of 0 [1][2][4]. In Git, rename detection calculates a similarity index between added and deleted files [1][2]. Setting this threshold to 0% means that Git requires 0% similarity between files to consider them a rename [1][2][4]. Because any file comparison will satisfy a 0% threshold, this effectively results in no files being identified as renames, acting as a functional disablement of the feature [1][2]. Summary of differences: --no-renames is the documented, idiomatic way to disable the feature [1][3]. --find-renames=0 is a side effect of how the similarity threshold parameter is parsed, which achieves the same result [1][4]. In summary, you should use --no-renames for clarity and intent [1][3].

Citations:


🌐 Web query:

git diff find-renames=0 rename output git-diff docs

💡 Result:

There is no standard git diff command option formatted as find-renames=0. The correct way to disable rename detection in git diff is to use the --no-renames flag [1][2][3]. If you are looking to control rename detection behavior, the standard options are as follows: --no-renames: Disables rename detection entirely [1][3]. This causes files that have been moved or renamed to be displayed as a deleted file and an added file in the diff output [4]. -M[] or --find-renames[=]: Enables rename detection [1][2]. The optional is a threshold representing the similarity index (as a percentage or a fraction) [1][3]. For example, -M50% (the default) requires at least 50% similarity for a file pair to be considered a rename [1][3]. Using -M100% limits detection to exact renames only [1][2]. Regarding your specific search for find-renames=0, it is possible you encountered this in a configuration context or as a misunderstood variant of the -M option. In git configuration, you can set diff.renames = false to disable rename detection globally or for specific repositories [2][5]. Note that the option syntax --find-renames=0 is generally not recognized by the command line interface in the same way as --no-renames; passing incorrect or non-standard options can sometimes result in them being ignored, causing the default rename detection behavior to remain active [6][7].

Citations:


🏁 Script executed:

#!/bin/bash
set -u

printf 'Repository files matching script name:\n'
git ls-files | grep -F 'scripts/validate-version-changes.mjs' || true

printf '\nRelevant lines from scripts/validate-version-changes.mjs:\n'
sed -n '1,90p' scripts/validate-version-changes.mjs

printf '\nCheck local git availability and parse option behavior via git --help/exit if possible (no repository code execution):\n'
if command -v git >/dev/null 2>&1; then
  printf 'git version: '
  git --version | sed -n '1p'
  git diff --help | grep -A3 -E '^\s+--find-renames|^\s+\-M|^\s+--no-renames' || true
else
  echo 'git not found'
fi

Repository: natanelia/comlink-worker-pool

Length of output: 3369


🌐 Web query:

git diff --no-renames --find-renames=0 official documentation -M threshold

💡 Result:

In Git, the command line options --no-renames and --find-renames operate as follows: --no-renames: This flag explicitly disables rename detection, overriding any configuration settings (such as diff.renames) that might otherwise enable it [1][2]. --find-renames[=]: This option enables rename detection. The optional parameter defines a threshold on the similarity index, which represents the amount of addition and deletion compared to the file's size [1][2]. Key details regarding the threshold : - Range: The threshold is expressed as a percentage or a fraction. For example, -M90% requires 90% similarity, while -M5 (interpreted as 0.5) is equivalent to 50% [1][3]. - Default: When -M or --find-renames is used without a specific value, the default similarity threshold is 50% [1][2]. - Behavior of 0: Setting the threshold to 0 (e.g., --find-renames=0) effectively sets the similarity requirement to 0%, meaning Git will consider any added/deleted pair as a rename, regardless of how much the file content has changed [1][2]. Note: Using both flags in the same command results in the last flag taking precedence. If you run git diff --no-renames --find-renames=0, Git will enable rename detection with a 0% threshold [1][2].

Citations:


🌐 Web query:

git source diff-lib.c rename_threshold zero find_renames false

💡 Result:

In the Git source code, rename detection logic is not primarily housed in diff-lib.c, which focuses on comparing the work tree with the index [1][2]. Rename detection is handled in diffcore-rename.c [3][4]. The parameters you mentioned, rename_threshold and find_renames, correspond to internal configuration settings within Git's diff machinery [3][4][5]. When Git's rename detection is disabled (find_renames = false), the system skips the expensive process of comparing file contents to detect similarities, significantly optimizing performance for diff operations [6][3]. Specifically, in the C implementation (such as in diffcore-rename.c): - find_renames is a function (or flag-based logic) that iterates through potential rename candidates [3][7][5]. - rename_threshold (often represented as minimum_score) determines the similarity percentage required for a file to be considered a rename [6][3][4]. - If rename detection is turned off, the diff output will only show additions and deletions rather than identifying moved or renamed files [6]. While diff-lib.c manages the high-level diff process by gathering file metadata and changes, it relies on functions defined in diffcore.h and implemented in files like diffcore-rename.c to perform the heavy lifting of rename detection [1][2][3].

Citations:


🌐 Web query:

"git diff" "--find-renames=0" "status R"

💡 Result:

The command git diff --find-renames=0 status R is likely a misunderstanding of how Git flags interact. This command does not perform a single operation, but rather interprets the flags as follows: 1. --find-renames=0: This option (equivalent to -M0) tells Git to detect renames with a similarity threshold of 0%. In practice, this forces Git to consider even very different files as potential renames [1][2][3]. 2. status: In the context of git diff, "status" is not a standard flag. Git will likely interpret this as a positional argument (a path or a commit reference), which will likely result in an error if no such file or commit exists [1][4]. 3. R: The -R flag (not to be confused with the status letter R) is a valid Git option that swaps the two inputs of the diff, showing differences from the index/on-disk file to the tree contents rather than the other way around [5][6][7]. If your goal was to show only renamed files, you should use the --name-status and --diff-filter options instead [5][6]. For example: git diff --name-status --diff-filter=R This command displays only the files that have been renamed [5][8][7]. If you specifically need to adjust rename detection sensitivity, you can add -M: git diff --name-status --diff-filter=R -M50% In this example, -M50% is the default behavior; you can adjust the percentage as needed to control how aggressively Git detects renames [1][3].

Citations:


🌐 Web query:

"git diff --name-status --no-renames" "R copy from"

💡 Result:

The command git diff --name-status --no-renames is used to list changed files along with their status, while explicitly disabling Git's rename detection mechanism [1][2][3]. Breakdown of the components: --name-status: This option instructs Git to output only the name of each changed file and a status letter indicating the type of change (e.g., A for Added, M for Modified, D for Deleted) [1][4][3]. It suppresses the detailed patch/diff content [5][4]. --no-renames: This option forces Git to turn off its rename detection logic [1][6][3]. Without this flag, Git might identify a file deletion and a file creation as a single "rename" (R) or "copy" (C) if the file contents are sufficiently similar [4][3]. By using --no-renames, you ensure that Git treats these as separate deletion (D) and addition (A) events [1][6]. This command is particularly useful when you need to see the raw file-level changes without Git attempting to infer relationships between renamed or copied files [1][3].

Citations:


🌐 Web query:

git 2.48 --find-renames=0 change rename detection zero threshold documentation

💡 Result:

In Git 2.48, as in other modern versions of Git, there is a distinction between rename detection threshold (controlled by -M or --find-renames) and the rename detection limit (controlled by -l or diff.renameLimit). 1. Rename Detection Threshold (--find-renames): The --find-renames option (or -M) controls the similarity index required to consider a file pair as a rename [1][2]. The value is a percentage or fraction (e.g., -M50% or -M0.5). If you set this to 0 (e.g., --find-renames=0), it effectively sets the required similarity threshold to 0%, meaning any deleted file and any added file would be considered a potential rename regardless of content similarity [1][2]. Note that this is different from disabling rename detection, which is done via --no-renames or git config diff.renames false [1][3]. 2. Rename Detection Limit (-l / diff.renameLimit): The -l option (or the diff.renameLimit configuration) controls the maximum number of files considered during the expensive, exhaustive O(N^2) portion of rename detection [1][2]. Contrary to the similarity threshold, setting this limit to 0 has a specific, well-documented behavior: it is treated as unlimited [1][2]. This allows Git to attempt exhaustive rename detection regardless of the number of files involved, which can be computationally expensive [1][4]. Summary: - --find-renames=0: Sets the similarity threshold to 0%. - -l0 or diff.renameLimit=0: Sets the exhaustive rename detection limit to unlimited [1][2].

Citations:


🌐 Web query:

"git diff --find-renames=0" "R" output

💡 Result:

The command git diff --find-renames=0 (or -M0) sets the rename detection threshold to 0% [1][2]. In Git's rename detection logic, the similarity index represents how much of a file's content remains unchanged between a deletion and an addition [1][3]. By setting this value to 0, you are instructing Git to consider any deleted file and any added file as a potential rename candidate, regardless of how different their contents are [4]. Essentially, this forces Git to perform the most aggressive rename detection possible, effectively treating any added/deleted file pair as a rename, even if they share almost no content [4]. This is in contrast to the default behavior (typically 50%), where Git only flags a rename if at least 50% of the content is identical [1][4]. While this can be useful for forcing detection in cases where files have changed significantly, it also increases the risk of false positives, where Git incorrectly identifies unrelated files as renames [4].

Citations:


🌐 Web query:

git diff --name-status --find-renames=0 changed files same status

💡 Result:

The command git diff --name-status --find-renames=0 is technically malformed because the --find-renames (or -M) option requires a similarity index threshold value [1][2]. Providing 0 as an argument to --find-renames is not a valid configuration for detecting renames [1][3]. Rename Detection Mechanics: 1. --find-renames[=]: This option is used to detect renames [1][4]. The parameter represents the similarity index threshold (as a percentage or fraction) [1][5]. 2. If the option is omitted or used without a value, the default similarity index is 50% [1][2]. 3. If you want to disable rename detection, you should use the --no-renames option instead [4][2]. If you run git diff --name-status, Git uses its default configuration (or the value set in the diff.renames git config) [1][6]. When rename detection is enabled, Git identifies a file as renamed (status R) if the similarity between a deleted file and an added file exceeds the defined threshold [1][7]. If rename detection is disabled, these changes will appear as two separate entries: a deletion (D) and an addition (A) [7]. To effectively control rename detection: - To disable: Use git diff --name-status --no-renames. - To use default threshold: Use git diff --name-status. - To set a specific threshold: Use git diff --name-status -M (e.g., -M60% or -M0.6) [1][5].

Citations:


🌐 Web query:

"git-diff" "--no-renames" documentation

💡 Result:

The --no-renames option in git-diff is a command-line flag used to disable Git's automatic rename detection [1][2][3]. When this flag is used, Git will not attempt to identify if a file has been renamed, even if the repository's configuration settings are set to enable it by default [1][3]. By default, Git may detect renames based on similarity thresholds between files [1][2]. Enabling --no-renames forces Git to treat such changes as a simple deletion of one file and the creation of another, bypassing the rename detection logic [1][2]. This option is particularly useful for overriding the diff.renames configuration variable in situations where you want to see the literal file additions and removals rather than a detected rename [1][3].

Citations:


🌐 Web query:

"git diff --name-status --find-renames=0"

💡 Result:

The command git diff --name-status --find-renames=0 is not a valid way to disable rename detection in Git [1][2]. In Git, the --find-renames (or -M) option expects a threshold value representing the similarity index (the percentage of the file that must remain unchanged to be considered a rename) [1][3]. Providing a value of 0 is not documented as a valid way to disable rename detection; rather, the default similarity index is 50%, and values are typically specified as percentages (e.g., -M50%) or fractions [1][4]. To explicitly disable rename detection in Git, you should use the --no-renames option instead [5]. Summary of components: --name-status: Displays only the status (e.g., Added, Modified, Deleted) and the filename of changed files [1][2]. --find-renames[=]: Used to control rename detection thresholds [1][3]. If you want to disable renames, use --no-renames [5].

Citations:


🌐 Web query:

Git 2.49 --find-renames=100 --find-renames=0 behavior

💡 Result:

In Git 2.49, as in previous versions, the --find-renames[=] option controls rename detection behavior using a similarity threshold [1][2]. --find-renames=100% This command forces Git to detect only exact renames [1][2]. A pair of files (a deletion and an addition) will be considered a rename only if the content is identical (100% similarity) [1][2]. --find-renames=0 Setting the threshold to 0 is technically equivalent to a very low similarity requirement [1][2]. According to Git documentation, if the number is provided without a percentage sign, it is treated as a fraction where -M5 equals 0.5 (50%) [1][2]. While a threshold of 0 technically asks for 0% similarity, Git's rename detection logic inherently requires some level of content overlap to identify a valid rename. In practice, this setting effectively disables the typical similarity filtering, potentially causing Git to report renames even for files with minimal shared content, or it may be treated as a request for basic rename detection depending on the specific implementation of the diffcore-rename engine [1][2]. There were no changes to the fundamental behavior of --find-renames in Git 2.49 [3][4]. The release focused on internal clean-ups, documentation format changes, and specific fixes rather than modifications to the rename detection algorithm or option parsing [3][4]. The default similarity index remains 50% [1][2].

Citations:


Use --no-renames to disable rename detection.

--find-renames=0 enables rename detection with a threshold instead of disabling it. Use git diff --name-status --no-renames so renamed files are emitted as A .../D rather than R.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/validate-version-changes.mjs` at line 29, Update the git diff
argument list containing “--find-renames=0” to use “--no-renames” instead,
ensuring rename detection is disabled and renamed files are reported as separate
additions and deletions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant