Skip to content

fix(stage): do not stage pre-existing untracked files#788

Merged
jdx merged 1 commit intomainfrom
fix/no-stage-untracked
Apr 1, 2026
Merged

fix(stage): do not stage pre-existing untracked files#788
jdx merged 1 commit intomainfrom
fix/no-stage-untracked

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Mar 31, 2026

Summary

  • hk fix was erroneously staging untracked files that existed before the hook ran
  • The staging logic treated all untracked files matching the stage pattern as candidates, without distinguishing pre-existing files from files newly created by a fixer
  • Track the initial set of untracked files in HookContext and exclude them from staging — files created by fixers during the run are still staged correctly

Fixes the issue reported in https://figma.slack.com/archives/C07MFSB8GCW/p1774975625557629

Test plan

  • Updated test/stage_job_files.bats to assert pre-existing untracked files are NOT staged
  • Updated test/overstaging_prettier.bats to assert pre-existing untracked files are NOT staged even with explicit stage globs
  • Verified stage=<JOB_FILES> still stages files newly created by fixers
  • Verified hk check still runs on untracked files (non-destructive)
  • All 145 cargo unit tests pass
  • All 15 directly related bats tests pass (both libgit2 and shell git backends)

🤖 Generated with Claude Code


Note

Medium Risk
Changes staging behavior in hk fix, which can affect what gets added to the git index; risk is limited to the staging filter logic and is covered by updated bats tests.

Overview
Prevents hk fix from staging pre-existing untracked files when using the default stage=<JOB_FILES> behavior by snapshotting the initial untracked set at hook start (HookContext.initial_untracked) and filtering those paths out during staging.

Keeps explicit stage globs as an opt-in to staging all matching untracked files, and updates bats tests to assert the new staging semantics for both <JOB_FILES> and explicit glob staging.

Written by Cursor Bugbot for commit c1d3d42. This will update automatically on new commits. Configure here.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request ensures that pre-existing untracked files are not automatically staged by the hook. It adds an "initial_untracked" set to the "HookContext" to capture the state of untracked files before execution and uses this set to filter files during the staging process. Tests have been updated to reflect this change in behavior. I have no feedback to provide.

`hk fix` was erroneously staging untracked files that existed before the
hook ran. This happened because the staging logic treated all untracked
files matching the stage pattern as candidates, without distinguishing
between pre-existing files and files newly created by a fixer.

Track the initial set of untracked files in HookContext and exclude them
from staging. Files created by fixers during the run are still staged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jdx jdx force-pushed the fix/no-stage-untracked branch from f99fb62 to c1d3d42 Compare March 31, 2026 21:15
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment thread src/step/execution.rs
!ctx.hook_ctx.initial_untracked.contains(p)
} else {
true
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Explicit globs still stage old untracked files

Medium Severity

stage filtering only checks initial_untracked when stage_only_job_files is true. With explicit globs, pre-existing untracked files still pass the filter and get staged, so hk fix can continue staging files that existed before the hook run.

Fix in Cursor Fix in Web

@jdx jdx marked this pull request as ready for review April 1, 2026 13:09
@jdx jdx merged commit 5caa5e6 into main Apr 1, 2026
21 checks passed
@jdx jdx deleted the fix/no-stage-untracked branch April 1, 2026 13:10
@jdx jdx mentioned this pull request Apr 1, 2026
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 1, 2026

Greptile Summary

This PR fixes hk fix from erroneously staging pre-existing untracked files by snapshotting git_status.untracked_files into HookContext.initial_untracked before the hook starts (and before any stash), then filtering that set out during the <JOB_FILES> staging pass. Explicit stage globs retain their opt-in behavior of staging all matching untracked files.

Key changes:

  • HookContext gains an immutable initial_untracked: BTreeSet<PathBuf> field set once before any stash, safe for concurrent reads from parallel steps.
  • The staging filter in execution.rs branches on stage_only_job_files: when true, an untracked candidate is only staged if it was absent from initial_untracked (i.e., created by a fixer during this run); when false (explicit glob), all matching untracked files are staged as before.
  • Existing pre_untracked local (post-fixer, pre-staging snapshot used to classify files as "created" vs "added") is still correct but may cause mild confusion alongside the new initial_untracked — renaming it to pre_staging_untracked would disambiguate.
  • Tests in stage_job_files.bats are updated with refute_line assertions to verify the fix; overstaging_prettier.bats receives a comment clarification only.

Confidence Score: 5/5

Safe to merge — the fix is narrowly scoped to the <JOB_FILES> staging filter, all edge cases (stash on/off, concurrent steps, explicit globs) are handled correctly, and the updated bats tests directly verify the new behavior.

No P0 or P1 issues found. Snapshot timing is correct (before stash). Type alignment between git_status.untracked_files (BTreeSet) and HookContext.initial_untracked is correct. Filter logic is logically equivalent to the old code for the explicit-glob path and correctly adds the pre-existing-untracked guard for the <JOB_FILES> path. The only finding is a minor naming suggestion (P2).

No files require special attention.

Important Files Changed

Filename Overview
src/hook.rs Adds initial_untracked: BTreeSet<PathBuf> field to HookContext, populated from git_status.untracked_files before any stash operation — snapshot timing is correct and the field is immutable after construction, making concurrent step access safe.
src/step/execution.rs Staging filter now distinguishes pre-existing vs newly created untracked files for <JOB_FILES> mode; explicit glob mode retains old opt-in behavior. Minor naming ambiguity between pre_untracked and initial_untracked.
test/stage_job_files.bats Test updated to assert src/untracked.sh is NOT staged (refute_line + remains ?? untracked) while src/script.sh still is — accurately captures the new semantics.
test/overstaging_prettier.bats Comment-only update clarifying that explicit stage globs opt into staging all matching untracked files; assertion is unchanged and still correct.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Hook starts] --> B[Fetch git_status\nuntracked_files snapshot]
    B --> C[HookContext::new\ninitial_untracked = snapshot]
    C --> D{HK_STASH_UNTRACKED?}
    D -- yes --> E[Stash unstaged + untracked]
    D -- no --> F[Fixers run]
    E --> F
    F --> G[Re-fetch current status\npost-fixer untracked_set]
    G --> H{stage_only_job_files?}
    H -- true: JOB_FILES --> I{File in untracked_set?}
    I -- yes --> J{File in initial_untracked?}
    J -- yes: pre-existing --> K[SKIP - do not stage]
    J -- no: newly created --> L[Stage file]
    I -- no: tracked-modified --> M{File in unstaged_set?}
    M -- yes --> L
    M -- no --> K
    H -- false: explicit glob --> N{File in untracked_set?}
    N -- yes: opt-in behavior --> L
    N -- no --> M
Loading

Comments Outside Diff (1)

  1. src/step/execution.rs, line 413-414 (link)

    P2 pre_untracked name could be confused with initial_untracked

    Both initial_untracked (hook-start snapshot) and pre_untracked (post-fixer, pre-staging snapshot) are now in scope. pre_untracked is used here to classify a staged file as either "created" (was untracked when staging began) or "added" (was a tracked-but-modified file). The name "pre-staging" would make the distinction clearer — without that, a reader could mistake it for the same hook-start snapshot as initial_untracked.

    And update the usage below:

    let created_paths: BTreeSet<PathBuf> =
        filtered_set.intersection(&pre_staging_untracked).cloned().collect();

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

    Fix in Claude Code

Fix All in Claude Code

Reviews (1): Last reviewed commit: "fix(stage): do not stage pre-existing un..." | Re-trigger Greptile

jdx added a commit that referenced this pull request Apr 1, 2026
### 🚀 Features

- **(betterleaks)** add betterleaks config to hk builtin config by
[@hituzi-no-sippo](https://github.com/hituzi-no-sippo) in
[#750](#750)
- **(builtins)** add google-java-format to builtins by
[@timothysparg](https://github.com/timothysparg) in
[#777](#777)
- **(builtins)** add dclint to builtins by
[@timothysparg](https://github.com/timothysparg) in
[#779](#779)
- **(config)** set default value for exclude to List() by
[@timothysparg](https://github.com/timothysparg) in
[#781](#781)
- **(core)** add required field to prevent unconfigured steps from
running by [@timothysparg](https://github.com/timothysparg) in
[#785](#785)
- **(gitleaks)** add gitleaks config to hk builtin config by
[@hituzi-no-sippo](https://github.com/hituzi-no-sippo) in
[#749](#749)
- **(mdschema)** add mdschema config to hk builtin config by
[@hituzi-no-sippo](https://github.com/hituzi-no-sippo) in
[#748](#748)
- **(pkl)** add pklr as opt-in pkl backend by
[@jdx](https://github.com/jdx) in
[#769](#769)
- add pklr as opt-in pkl backend by [@jdx](https://github.com/jdx) in
[#768](#768)

### 🐛 Bug Fixes

- **(docs)** replace invalid /latest/ pkl package URIs with versioned
format by [@jdx](https://github.com/jdx) in
[#770](#770)
- **(stage)** do not stage pre-existing untracked files by
[@jdx](https://github.com/jdx) in
[#788](#788)

### 📚 Documentation

- add benchmarks page and reproducible benchmark suite by
[@jdx](https://github.com/jdx) in
[#766](#766)
- add recommended setup section to mise integration by
[@timothysparg](https://github.com/timothysparg) in
[#780](#780)

### 📦️ Dependency Updates

- lock file maintenance by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#762](#762)
- update rust crate pklr to 0.4 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#776](#776)
- update apple-actions/import-codesign-certs digest to fe74d46 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#774](#774)
- update anthropics/claude-code-action digest to 094bd24 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#773](#773)
- update taiki-e/upload-rust-binary-action digest to 0e34102 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#775](#775)
- bump usage to 3.2.0 and pkl to 0.31.1, add windows platforms by
[@jdx](https://github.com/jdx) in
[#787](#787)
- lock file maintenance by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#786](#786)

### New Contributors

- @timothysparg made their first contribution in
[#781](#781)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Primarily a version/documentation bump, but it also updates the Rust
dependency lockfile (e.g., `hyper` and `windows-sys`), which could
introduce build/runtime regressions.
> 
> **Overview**
> Bumps hk to **v1.40.0** and publishes the corresponding release notes
in `CHANGELOG.md`.
> 
> Updates generated CLI/docs and all Pkl package URL references in
docs/examples to point at `v1.40.0`, and refreshes `Cargo.lock` with
dependency updates/removals consistent with the new release.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
da00ab8. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: mise-en-dev <123107610+mise-en-dev@users.noreply.github.com>
tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Apr 2, 2026
This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [hk](https://github.com/jdx/hk) | minor | `1.39.0` → `1.40.0` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>jdx/hk (hk)</summary>

### [`v1.40.0`](https://github.com/jdx/hk/blob/HEAD/CHANGELOG.md#1400---2026-04-01)

[Compare Source](jdx/hk@v1.39.0...v1.40.0)

##### 🚀 Features

- **(betterleaks)** add betterleaks config to hk builtin config by [@&#8203;hituzi-no-sippo](https://github.com/hituzi-no-sippo) in [#&#8203;750](jdx/hk#750)
- **(builtins)** add google-java-format to builtins by [@&#8203;timothysparg](https://github.com/timothysparg) in [#&#8203;777](jdx/hk#777)
- **(builtins)** add dclint to builtins by [@&#8203;timothysparg](https://github.com/timothysparg) in [#&#8203;779](jdx/hk#779)
- **(config)** set default value for exclude to List() by [@&#8203;timothysparg](https://github.com/timothysparg) in [#&#8203;781](jdx/hk#781)
- **(core)** add required field to prevent unconfigured steps from running by [@&#8203;timothysparg](https://github.com/timothysparg) in [#&#8203;785](jdx/hk#785)
- **(gitleaks)** add gitleaks config to hk builtin config by [@&#8203;hituzi-no-sippo](https://github.com/hituzi-no-sippo) in [#&#8203;749](jdx/hk#749)
- **(mdschema)** add mdschema config to hk builtin config by [@&#8203;hituzi-no-sippo](https://github.com/hituzi-no-sippo) in [#&#8203;748](jdx/hk#748)
- **(pkl)** add pklr as opt-in pkl backend by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;769](jdx/hk#769)
- add pklr as opt-in pkl backend by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;768](jdx/hk#768)

##### 🐛 Bug Fixes

- **(docs)** replace invalid /latest/ pkl package URIs with versioned format by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;770](jdx/hk#770)
- **(stage)** do not stage pre-existing untracked files by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;788](jdx/hk#788)

##### 📚 Documentation

- add benchmarks page and reproducible benchmark suite by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;766](jdx/hk#766)
- add recommended setup section to mise integration by [@&#8203;timothysparg](https://github.com/timothysparg) in [#&#8203;780](jdx/hk#780)

##### 📦️ Dependency Updates

- lock file maintenance by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;762](jdx/hk#762)
- update rust crate pklr to 0.4 by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;776](jdx/hk#776)
- update apple-actions/import-codesign-certs digest to [`fe74d46`](jdx/hk@fe74d46) by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;774](jdx/hk#774)
- update anthropics/claude-code-action digest to [`094bd24`](jdx/hk@094bd24) by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;773](jdx/hk#773)
- update taiki-e/upload-rust-binary-action digest to [`0e34102`](jdx/hk@0e34102) by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;775](jdx/hk#775)
- bump usage to 3.2.0 and pkl to 0.31.1, add windows platforms by [@&#8203;jdx](https://github.com/jdx) in [#&#8203;787](jdx/hk#787)
- lock file maintenance by [@&#8203;renovate\[bot\]](https://github.com/renovate\[bot]) in [#&#8203;786](jdx/hk#786)

##### New Contributors

- [@&#8203;timothysparg](https://github.com/timothysparg) made their first contribution in [#&#8203;781](jdx/hk#781)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDIuMTAiLCJ1cGRhdGVkSW5WZXIiOiI0My4xMDIuMTAiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbIlJlbm92YXRlIEJvdCIsImF1dG9tYXRpb246Ym90LWF1dGhvcmVkIiwiZGVwZW5kZW5jeS10eXBlOjptaW5vciJdfQ==-->
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