Skip to content

Bits of .NET

Daily micro-tips for C#, SQL, performance, and scalable backend engineering.

  • Asp.Net Core
  • C#
  • SQL
  • JavaScript
  • CSS
  • About
  • ErcanOPAK.com
  • No Access
  • Privacy Policy
Git

Git: Fix a Pre-Push Hook That Silently Does Nothing Because It Lost Its Executable Bit After Cloning on Windows

- 04.10.26 - ErcanOPAK

πŸ”€ The Hook That Was There, and Did Nothing

A pre-push hook committed to a repository as a plain script file needs its executable permission bit set before Git will actually run it, and that bit is a Unix filesystem attribute that a checkout on Windows has no real equivalent for – the file arrives on disk looking completely normal, with the correct content and the correct name in the correct hooks folder, but without the permission Git checks for before invoking it. Git does not report an error when a hook exists but lacks the executable bit; it simply skips running it, so a push-time check meant to catch a broken build or an accidental commit of a secret file passes every single push without ever actually running once.

πŸ”Ž The Problem

$ ls -la .git/hooks/pre-push
-rw-r--r--  1 dev  staff  412 Oct  2 09:14 pre-push
# Missing the executable bit (no 'x' in the permission string) -
# this file was checked out on Windows, where there is no real
# equivalent concept, and the clone on a teammate's Linux or macOS
# machine inherited whatever permission the checkout produced.

$ git push origin feature/billing-fix
Enumerating objects: 9, done.
...
 * [new branch]      feature/billing-fix -> feature/billing-fix
# Push succeeds immediately - no hook output at all, because Git
# silently skipped a hook file it found but could not execute,
# rather than failing the push or printing any warning about it.

$ cat .git/hooks/pre-push
#!/bin/sh
# runs the test suite and blocks the push on failure - except it
# never actually ran, on this clone, even once.

βœ… Fix: Make the Executable Bit Part of the Setup, Not an Assumption

  • Run a chmod command to restore the executable bit as an explicit step in the project’s documented setup process, right after cloning, rather than assuming the bit survives every combination of operating systems and checkout tools involved.
  • Distribute the hook through a setup script or a Git hooks manager that sets the executable permission itself at install time, instead of relying on the bit being preserved correctly by whichever clone or checkout path a given teammate happens to use.
  • Add a one-line check to CI, or to the setup script itself, that verifies the hook file is actually executable right after it is installed, so a missing permission bit gets caught immediately instead of silently producing a hook that never runs.

⚠️ Why This Is Easy to Miss

  • Git skips a non-executable hook without printing any message at all, so a push that was supposed to be checked looks identical, from the command line, to a push where the hook ran cleanly and passed – there is no visible difference between the two outcomes.
  • The permission bit question only comes up at all on a cross-platform team, or when a repository gets cloned through a tool or a Windows filesystem that does not preserve Unix permissions the way a native Linux or macOS checkout normally does.

A hook Git can’t execute is a hook Git won’t run – and it won’t tell you either, because from Git’s point of view, skipping it isn’t an error.

β€” Version Control Tip

Related posts:

Git Hooks Break CI Unexpectedly

The Hidden Cost of Large Commits (It’s Not Code Review)

Git β€” git stash Can Lose Untracked Files

Post Views: 0

Post navigation

The AI Prompt That Finds Race Conditions in Code That Only Fails Under Concurrent Load
C#: Fix a Lazy Initialization That Silently Runs Its Factory Twice Because the Thread-Safety Mode Was Never Set

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

October 2026
M T W T F S S
 1234
567891011
12131415161718
19202122232425
262728293031  
« Sep    

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (973)
  • How to make theater mode the default for Youtube (947)
  • How to add default value for Entity Framework migrations for DateTime and Bool (938)
  • Get the First and Last Word from a String or Sentence in SQL (847)
  • How to select distinct rows in a datatable in C# (836)
  • How to enable, disable and check if Service Broker is enabled on a database in SQL Server (624)
  • Add Constraint to SQL Table to ensure email contains @ (590)
  • Average of all values in a column that are not zero in SQL (553)
  • How to use Map Mode for Vertical Scroll Mode in Visual Studio (526)
  • Find numbers with more than two decimal places in SQL (468)

Recent Posts

  • CSS: Fix a prefers-color-scheme Media Query That Gets Silently Overridden by a Browser Extension’s Forced Dark Mode
  • Git: Fix a Merge Commit That Silently Drops a File Because Both Branches Deleted It Differently
  • HTML5: Fix a Native Lazy-Loading Image That Never Loads Because It Sits Inside a Hidden Tab Until the User Clicks It
  • The AI Prompt That Traces a Null Reference Exception Back to the Exact Line That First Produced the Null
  • The AI Prompt That Turns a Gym Membership Contract’s Fine Print Into a Plain-English List of Cancellation Steps
  • Photoshop: Fix a Color Profile Mismatch That Makes Printed Output Look Nothing Like What You Saw On Screen
  • WordPress: Fix Search Results That Return Pages From a Theme You Deactivated Months Ago
  • Visual Studio: Fix a Test Project That Builds Fine Alone but Fails to Discover Any Tests After a NuGet Restore
  • ASP.NET Core: Fix a File Upload That Times Out on Slow Connections Only Because Kestrel’s Minimum Data Rate Feature Kicked In
  • JavaScript: Fix an Array Destructuring Default Value That Silently Never Applies Because null Was Passed Instead of Undefined

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (973)
  • How to make theater mode the default for Youtube (947)
  • How to add default value for Entity Framework migrations for DateTime and Bool (938)
  • Get the First and Last Word from a String or Sentence in SQL (847)
  • How to select distinct rows in a datatable in C# (836)

Recent Posts

  • CSS: Fix a prefers-color-scheme Media Query That Gets Silently Overridden by a Browser Extension’s Forced Dark Mode
  • Git: Fix a Merge Commit That Silently Drops a File Because Both Branches Deleted It Differently
  • HTML5: Fix a Native Lazy-Loading Image That Never Loads Because It Sits Inside a Hidden Tab Until the User Clicks It
  • The AI Prompt That Traces a Null Reference Exception Back to the Exact Line That First Produced the Null
  • The AI Prompt That Turns a Gym Membership Contract’s Fine Print Into a Plain-English List of Cancellation Steps

Social

  • ErcanOPAK.com
  • GoodReads
  • LetterBoxD
  • Linkedin
  • The Blog
  • Twitter
© 2026 Bits of .NET | Built with Xblog Plus free WordPress theme by wpthemespace.com