Cursor Pre-Commit Hooks Not Running: The Source Control Bug (2026)

If you commit through the Source Control panel in Cursor, your pre-commit hook has not run since the first week of August. No error. No warning. No --no-verify anywhere in the logged command. The commit just succeeds, faster than it used to, and the fastest way to notice is that it stopped feeling slow.

Cursor staff confirmed it on 10 August 2026. As of today it is still there.

TL;DR

Cursor 3.15.6 began injecting core.hooksPath, pointed at the platform's null device, into every git call the Source Control panel makes. Git then finds no hooks and runs none: pre-commit, commit-msg, pre-push, and everything they invoke. Commit the same staged changes from the integrated terminal and your hooks run normally. That is the workaround Cursor staff gave on 12 August, and there is still no fix.

What is actually happening

Cursor's bundled git extension appends config overrides to each git invocation using GIT_CONFIG_KEY_n / GIT_CONFIG_VALUE_n environment variables. One of them sets core.hooksPath to Node's os.devNull, which is /dev/null on macOS and Linux and \\.\nul on Windows. Git dutifully looks for hooks in a place where there are none.

A Cursor staff member confirmed the mechanism directly. Responding on 2026-08-10 in a second thread, to a user who had decompiled the bundled extension:

The decompilation and your conclusion about the GIT_CONFIG_* env vars are spot on, that's exactly what's happening.

A second staff member, the same day, in the main bug thread:

Thanks for the detailed report, what you're seeing isn't intended. Source Control Commit is skipping hooks right now; the same commit from the terminal should still run them. We've let the team know and this is an issue we're tracking.

Worth being precise about intent: core.hooksPath is not the only thing in that injected array. Reporters also found safe.bareRepository, core.fsmonitor and core.attributesFile alongside it, which reads like a hardening or performance patch that swept up one setting it should not have. Nobody has suggested this was deliberate, and staff said the opposite.

We tested the mechanism

The forum reports describe the symptom. We wanted to confirm that core.hooksPath set to the null device is sufficient on its own, and, more importantly, whether a developer could tell afterwards.

Git 2.43.0, a fresh repo, and a pre-commit hook that does nothing but refuse:

The hook
#!/bin/sh
echo "PRE-COMMIT HOOK RAN"
exit 1
Three commits, three outcomes
$ git commit -m one
PRE-COMMIT HOOK RAN
exit=1

$ git -c core.hooksPath=/dev/null commit -m two
[master (root-commit) 2c399a8] two
exit=0

$ GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=core.hooksPath \
  GIT_CONFIG_VALUE_0=/dev/null git commit -m three
[master 1666cc0] three
exit=0

Both bypass forms create the commit, exit 0, and print nothing about the hook. That part matches the reports. Here is the part that makes this worth writing about:

What git config says afterwards
$ git config --get core.hooksPath
$ echo $?
1

Nothing. Exit 1, meaning the key is not set. The override lives for the duration of one command and leaves no trace in your global config, your repo config, or anywhere else you would think to look. A developer who suspects something is wrong and goes hunting through git config will find their setup is fine, because it is.

This is why it went three weeks without a fix and why it is worth checking rather than assuming. There is no artifact. The only evidence is a hook that did not fire, and a hook not firing looks exactly like a hook that had nothing to say.

What it turns off

Every git hook, and with them everything those hooks invoke. In practice, for this audience, that is:

What you set upWhat it was doingNow
pre-commit running gitleaks or trufflehogBlocking a commit that contains an API keyDoes not run
Husky-managed chainsWhatever the chain does, in orderDoes not run
lint-stagedFormatting and linting staged filesDoes not run
commit-msgEnforcing conventional commit formatDoes not run
pre-pushLast check before code leaves your machineDoes not run

Husky is a hook manager, not a hook. It installs a pre-commit file, and that file is what git is no longer finding. Nothing in your Husky config needs changing, which is exactly why people spend an afternoon changing it.

The one a security-minded reader should care about is the first row. A reporter on 26 August named it directly:

This is especially dangerous because Git hooks may enforce formatting, linting, commit-message validation, secret scanning, or other repository policies.

Scope that honestly, because the scope matters. The check is bypassed only for commits made through Cursor's Source Control panel, on affected versions. Terminal commits are unaffected. Other git clients are unaffected. GitHub push protection, and any secret scanning that runs in CI, are unaffected. Nobody has reported an actual secret reaching a repository through this.

And a local pre-commit hook was never your strongest control anyway. Anyone can pass --no-verify on any day of the week. But it is the control most commonly recommended to founders shipping AI-built code, it is the one most people believe is watching, and right now, for the click most people use, it is off.

Check your own setup in sixty seconds

1

In your repo, create a hook that refuses everything:

printf '#!/bin/sh\nexit 1\n' > .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit

If you use Husky, point at its directory instead: .husky/pre-commit.

2

Stage any trivial change, then commit from the Source Control panel, the checkmark button in the sidebar.

3

If the commit succeeds, your hooks are being skipped. A working setup refuses the commit with a non-zero exit.

4

Run the same commit from the integrated terminal with git commit -m test. It should be blocked. That difference is the bug, and it is also your workaround.

5

Delete the test hook. Then decide what to do about the gap.

What to do about it

Commit from the terminal for now. This is what Cursor staff recommended on 12 August, and it works because the environment variables are injected into the extension's own child process rather than into your shell.

Move secret scanning off your laptop. The durable fix here has nothing to do with Cursor. A control that lives in .git/hooks is opt-in per machine, skippable with one flag, absent on a fresh clone, and, as this shows, disableable by your editor without telling you. Put the same check somewhere it cannot be waved through:

  • GitHub push protection rejects the push itself, server-side. Free on public repositories.
  • Gitleaks in CI runs on the pull request, on GitHub's machines, whatever your editor did.
  • Both together, with the pre-commit hook kept as the fast local convenience it always was.

Our git secret scanning guide covers setting those up. The short version: if losing one local hook meaningfully weakens your position, the hook was carrying more weight than it should have been.

Check what already shipped. If you have been committing through the panel since early August and you relied on that hook, the useful question is not whether the hook will run tomorrow. It is whether anything got through in the meantime. Scanning git history for secrets is a different job from blocking new ones, and this is the situation it exists for.

If you find a key that shipped, rotating it comes first and rewriting history comes second. A key that reached a remote must be treated as public, whether or not you can scrub the commit.

Is this a VS Code bug?

No, and it is worth being clear about that since Cursor is a VS Code fork.

We checked the upstream source. The git extension in microsoft/vscode sets exactly one environment variable, GIT_OPTIONAL_LOCKS=0. The strings hooksPath, GIT_CONFIG_KEY and devNull do not appear in extensions/git/src/git.ts or anywhere else in that repository, and there is no equivalent upstream issue. This is a change in Cursor's fork.

Where it stands

The main thread was last active on 26 August 2026. It is still open, has no accepted solution, and has had no staff reply since 12 August. Users have reported the bug persisting in 3.16.29 (20 August), 3.17.8 (23 August) and 3.17.21 (25 August), that last one on the day the build shipped.

On the version that introduced it: a release tracker polling Cursor's update endpoint shows 3.14.27 was still the stable build on 5 August, and the first forum report of 3.15.6 arrived on 7 August. So it landed somewhere in that window. Cursor's public changelog does not list patch versions, so there is no official release date to cite, and its August entries do not mention the git extension at all.

This post describes the state of an open bug as of 2026-08-27. Check the forum thread before assuming it still applies. A fix would land in a Cursor release rather than in anything you configure.

Why is my pre-commit hook not running in Cursor?

Since Cursor 3.15.6, commits made through the Source Control panel run with core.hooksPath pointed at the platform's null device, so git finds no hooks to run. Cursor staff confirmed on 2026-08-10 that this is not intended. Committing the same staged changes from the integrated terminal still runs hooks.

Is Husky broken in Cursor?

Husky itself is fine. It installs a pre-commit hook, and that hook is what gets skipped. The same goes for anything a hook invokes, including lint-staged and gitleaks. Nothing in your Husky config needs changing, which is the frustrating part.

Which Cursor versions are affected?

It starts at 3.15.6, which reached the stable channel in the first week of August 2026. Users have since reported it still present in 3.16.29, 3.17.8 and 3.17.21, the last of those on the day that build shipped. No fix has been announced as of 2026-08-27.

How do I check whether my hooks are running?

Add a pre-commit hook containing only exit 1, stage a change, and commit from the Source Control panel. If the commit succeeds, hooks are being skipped. Then run the same commit from the terminal and watch it get blocked. Delete the hook afterwards.

Does this affect VS Code?

No. The git extension in microsoft/vscode sets one environment variable, GIT_OPTIONAL_LOCKS. The strings hooksPath, GIT_CONFIG_KEY and devNull do not appear in that extension's source at all, so this is a change in Cursor's fork rather than upstream behaviour.

Did Anything Slip Through?

CheckYourVibe reads your deployed app the way a stranger would and flags the keys that made it into the bundle, whatever your editor did on the way there.

How-To Guides

Cursor Pre-Commit Hooks Not Running: The Source Control Bug (2026)