Skip to content

fix(action): hashFiles must use GITHUB_WORKSPACE#2

Merged
tcely merged 3 commits intomainfrom
tcely-hash-lock-files
Mar 16, 2026
Merged

fix(action): hashFiles must use GITHUB_WORKSPACE#2
tcely merged 3 commits intomainfrom
tcely-hash-lock-files

Conversation

@tcely
Copy link
Copy Markdown
Owner

@tcely tcely commented Mar 16, 2026

No description provided.

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Use glob patterns for lock file hashing

🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Replace specific lock file names with glob patterns
• Use *.lock to match all lock files generically
• Use *-lock.json to match npm-style lock files
• Improves cache key flexibility for different package managers
Diagram
flowchart LR
  A["Specific lock files<br/>bun.lock, package-lock.json"] -- "Replace with globs" --> B["Generic patterns<br/>*.lock, *-lock.json"]
  B -- "Improves cache key" --> C["Better multi-package-manager support"]
Loading

Grey Divider

File Changes

1. action.yml 🐞 Bug fix +1/-1

Update cache key glob patterns for lock files

• Changed hashFiles() glob patterns from specific lock file names to generic patterns
• Replaced bun.lock with *.lock to match any lock file
• Replaced package-lock.json with *-lock.json to match npm and similar lock files
• Maintains src/**/*.ts and package.json patterns unchanged

action.yml


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Mar 16, 2026

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Cache key hashes wrong repo🐞 Bug ⛯ Reliability
Description
The cache key is computed from hashFiles('src/**/*.ts', '*.lock', '*-lock.json', 'package.json'),
but the binary is built/cached under ${{ github.action_path }}, so action source/lockfile changes
won’t reliably change the key. With the documented remote usage (no prior checkout), the action
computes the key before any checkout, so hashFiles(...) will typically match nothing and can
restore a stale binary across action version updates.
Code

action.yml[38]

+        key: no-response-bin-${{ runner.os }}-${{ hashFiles('src/**/*.ts', '*.lock', '*-lock.json', 'package.json') }}
Evidence
The cache stores and builds the binary from github.action_path, but the key hashes files that (a)
are not scoped to github.action_path, and (b) in the documented usage aren’t even present in the
workspace at the time the cache key is computed (checkout happens only after the cache step, and
only on cache-miss). Therefore the new glob patterns do not ensure the cache invalidates when the
action’s own lockfile/source changes, and can cause stale binary restores.

action.yml[33-39]
action.yml[40-43]
action.yml[48-57]
README.md[24-31]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The cache key is derived from `hashFiles(...)` patterns that are not tied to `${{ github.action_path }}`, but the cached artifact is `${{ github.action_path }}/bin/no-response-bin`. When the action is used remotely as documented (no prior checkout), `hashFiles(...)` will usually match nothing at cache-key time, producing a mostly-static key and allowing stale binaries to be restored across action updates.
### Issue Context
- The action is used remotely (`uses: tcely/no-response@...`).
- The action’s build and cached binary live under `github.action_path`.
- The cache key should track the action’s own source/deps (or at least its ref/sha), not the caller repository’s files.
### Fix Focus Areas
- action.yml[33-57]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread action.yml Outdated
@tcely

This comment was marked as outdated.

@qodo-code-review
Copy link
Copy Markdown

Ask

How could we do without a checkout before the cache ?

Answer:

You can avoid a checkout before the cache as long as the cache key doesn’t depend on files that only exist in GITHUB_WORKSPACE.

Today the cache key is built with hashFiles('src/**/*.ts', '*.lock', '*-lock.json', 'package.json'). hashFiles() matches paths relative to the workspace by default, so without a prior actions/checkout, those files won’t exist and the hash will be empty/incorrect.

Two ways to remove the pre-cache checkout:

  1. Hash files from github.action_path (recommended)
    The composite action code is already present on disk at ${{ github.action_path }} when the action runs, even if the caller repo hasn’t been checked out. So point hashFiles() at that directory:

    - name: Cache Compiled Binary
      id: cache-binary
      uses: actions/cache@v5
      with:
        path: ${{ github.action_path }}/bin/no-response-bin
        key: no-response-bin-${{ runner.os }}-${{ hashFiles(
          format('{0}/src/**/*.ts', github.action_path),
          format('{0}/*.lock', github.action_path),
          format('{0}/*-lock.json', github.action_path),
          format('{0}/package.json', github.action_path)
        ) }}

    With that change, you can keep checkout only if/when you actually need the caller repository later.

  2. Compute your own hash in a run step (more verbose, more control)
    E.g., find ${{ github.action_path }} -name ... -print0 | sort -z | xargs -0 sha256sum | sha256sum and use that as the key. This avoids any ambiguity about hashFiles() path semantics.

Net: actions/cache itself doesn’t require checkout; only your key-generation does. Point the hash patterns at github.action_path and you can drop the pre-cache checkout entirely.

@tcely

This comment was marked as outdated.

@qodo-code-review
Copy link
Copy Markdown

Ask

How can we use hashFiles?

hashFiles(path)

Returns a single hash for the set of files that matches the path pattern. You can provide a single path pattern or multiple path patterns separated by commas. The path is relative to the GITHUB_WORKSPACE directory and can only include files inside of the GITHUB_WORKSPACE. This function calculates an individual SHA-256 hash for each matched file, and then uses those hashes to calculate a final SHA-256 hash for the set of files. If the path pattern does not match any files, this returns an empty string.

With the restrictions about using only files inside GITHUB_WORKSPACE a separate step to copy files into an appropriate location seems to be required.

Answer:

hashFiles() can only see files under GITHUB_WORKSPACE, so the way to “use hashFiles” is to ensure the files you want to hash actually exist in the workspace before you evaluate the expression.

In this PR, moving actions/checkout before the cache step is exactly what makes the cache key work, because now src/**/*.ts, *.lock, *-lock.json, and package.json are present in the workspace when the key is computed. Without checkout first, hashFiles(...) would likely return an empty string (no matches), leading to a poor cache key.

Where the “copy/checkout elsewhere” concern becomes real is when the files you want to hash live in the action’s directory (github.action_path) rather than the repository workspace. Since hashFiles() cannot read ${{ github.action_path }} (it’s outside the workspace), you have two practical options:

  1. Checkout the action repo into the workspace (recommended if this action is used remotely)

    - uses: actions/checkout@v6
      with:
        repository: <owner>/<action-repo>
        ref: <tag-or-sha>
        path: action-src  # this folder is inside GITHUB_WORKSPACE
    
    - uses: actions/cache@v5
      with:
        path: ${{ github.action_path }}/bin/no-response-bin
        key: no-response-bin-${{ runner.os }}-${{ hashFiles('action-src/src/**/*.ts', 'action-src/*.lock', 'action-src/*-lock.json', 'action-src/package.json') }}

    This keeps everything hashable because it’s now under GITHUB_WORKSPACE/action-src/....

  2. Copy the relevant files from github.action_path into a workspace folder and hash that

    - run: |
        mkdir -p .action-hash-input
        cp -R "${{ github.action_path }}/src" .action-hash-input/
        cp "${{ github.action_path }}/package.json" .action-hash-input/ || true
        cp "${{ github.action_path }}"/*.lock .action-hash-input/ || true
        cp "${{ github.action_path }}"/*-lock.json .action-hash-input/ || true
      shell: bash
    
    - uses: actions/cache@v5
      with:
        path: ${{ github.action_path }}/bin/no-response-bin
        key: no-response-bin-${{ runner.os }}-${{ hashFiles('.action-hash-input/**') }}

    This is a bit more work/fragile, but it avoids checking out a separate repo.

So: no extra copy step is needed as long as the hashed inputs are in the checked-out repo (which the PR now enforces by checking out first). If the intent is to hash the action’s own source when used as a remote action, then yes—you need either a secondary checkout into the workspace or a copy into the workspace, because hashFiles() cannot operate on github.action_path.

@tcely tcely changed the title fix(action): use globs for lock files fix(action): hashFiles must use GITHUB_WORKSPACE Mar 16, 2026
@tcely tcely merged commit 963897d into main Mar 16, 2026
1 check passed
@tcely tcely deleted the tcely-hash-lock-files branch March 16, 2026 18:32
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