Why do hosted Git services rarely let tenants install raw pre-receive hooks on a repository?
answer
- A hook is not configuration, it is a program
- Whose machine is executing it
- Every push waits for it
- One broken script, no more pushes
- Constrained rules can be validated up front
basics
~20 sA pre-receive hook is arbitrary code executed by the service on its own machines, inside the path of every push, with the repository's data in reach. Hosted services therefore expose declarative push rules they can evaluate safely and predictably instead.
solid answer
~50 sThink about what a `pre-receive` hook actually is: an executable file in the receiving repository's hook directory that the receiving process runs, synchronously, before any ref moves. On a machine you own that is a feature. On someone else's multi-tenant infrastructure it means a tenant supplying arbitrary code that runs on the provider's hosts, in the latency path of every push, with access to the repository data next to it — and with a failure mode where one broken script rejects every push to that repository. It also cannot be reviewed, versioned or reasoned about by the platform, and it does not survive the repository being moved or replicated between hosts. So hosted services generally keep the raw hook mechanism to themselves and expose a constrained, declarative equivalent, which they can validate, evaluate cheaply and apply consistently. The Git-level lesson stands either way: the receiving side is the only place a push rule can actually bind.
go deeper
The takeaway to hold on to is simply that a receiving-side hook is a program the server runs, not a setting — which is why a service that hosts many customers is cautious about it.
Be able to state the mechanism precisely: hooks are executables the receiving repository runs before refs move, with no sandbox and no resource limits, in the path of every push.
Show operational awareness: the hook is on everyone's critical path, its own failures look like rejections and block all pushes, and its cost scales with what it walks.
Own the tradeoff between an expressive escape hatch and an operable, auditable rule, and be able to say which control you would accept as genuinely enforced and who is carrying the risk for it.
## What the mechanism really is Git's receiving-side hooks are not a configuration surface. `pre-receive`, `update` and `post-receive` are executables in the receiving repository's hook directory, which `git receive-pack` runs when a push arrives. There is no sandbox, no declared interface beyond arguments and stdin, no resource limit and no language restriction. Whoever can write a file into that directory can run arbitrary code as the user that serves pushes. On a repository you administer, that unbounded power is precisely the point — it is how you express a rule Git has no built-in setting for. The interesting question is what changes when the repository lives on infrastructure somebody else operates. ## Four properties that do not survive multi-tenancy **Arbitrary code execution.** The hook runs as whatever user serves the push, on the provider's machine, with the repository's objects and refs at hand. Isolating that per tenant is not a small feature: it is a container boundary, a filesystem policy, a network policy and a resource limit around a step that used to be a `fork` and an `exec`. **Latency in the critical path.** Every push waits for the hook. A script that shells out to a slow network endpoint holds the connection open for a developer somewhere, and multiplied across tenants it becomes an availability problem for the push path itself — the one operation everyone depends on. **Failure amplification.** A non-zero exit rejects the push; a hook that crashes, hits a syntax error or exhausts memory exits non-zero. So a single mistake in a tenant's script turns into "nobody can push to this repository", and the tenant will report it as an outage of the service. **Non-portability of the deployment.** Hosted repositories move: they are replicated, resharded, migrated between hosts. A file sitting in a hook directory is host-local state with no version history, no review trail and no natural home in the repository's own content, so it has to be replicated and reconciled as a separate concern. ## Why a declarative rule is the tractable alternative If you accept those constraints, the design that falls out is a constrained rule language rather than a script. Declarative rules can be validated when they are written rather than when a push arrives, evaluated with bounded cost, reasoned about by the provider, recorded with an audit trail, and applied uniformly wherever the repository happens to be served. The price is expressiveness: anything the rule language cannot say, you cannot enforce that way — which is exactly why self-managed and enterprise deployments, where the operator *is* the tenant, tend to keep raw hooks available. ## What stays true regardless The Git-level substance does not change with the hosting model, and that is the part worth carrying into any interview answer: - The receiving side is the only place a push rule can bind. A client's `--no-verify` cannot reach it, and no local configuration can turn it off. - The vetoing hooks run **before** any ref is updated: `pre-receive` once for the push, `update` once per ref. Anything after that can only report. - Rejection semantics differ by hook — a non-zero `pre-receive` blocks every ref in the push, while `update` blocks a single one. - Whatever evaluates the rule sits in every contributor's push path, so its cost and its reliability are everyone's problem. ## The judgment being examined This is a question about where a control belongs, and the strong answer separates the *mechanism* from the *venue*. A rule enforced at the point changes are received is the only one you can honestly call enforced; how that enforcement is expressed — a script you own, or a declarative rule a provider evaluates — is a tradeoff between expressiveness and operability, and the shape of that tradeoff is set by who is carrying the operational risk. A candidate who can say plainly "a raw hook is arbitrary code in the push path of a shared system, so the constrained form exists to make it operable" has understood both halves. Be careful not to overclaim about any particular provider's feature set; the durable reasoning is about the mechanism, not about a settings screen.
- Which parts of your answer are about Git and which are about hosting?Git contributes the invariants: hooks run on the receiving repository, the vetoing ones run before any ref moves, `pre-receive` rejects the whole push and `update` rejects one ref, and no client flag can skip any of them. Hosting contributes the constraints — multi-tenancy, latency in a shared push path, failure amplification, and repositories that move between machines.
- When is running your own pre-receive hook clearly the right call?When you operate the receiving repository yourself and the rule cannot be expressed any other way — a policy that must inspect the actual commit contents, or one that has to consider the whole push atomically. You are then carrying the operational risk knowingly, which is the honest condition for holding that much power in the push path.
- What is the biggest operational risk of a hand-written pre-receive hook?That its own failures are indistinguishable from rejections. A crash, a missing interpreter or an unreachable dependency all produce a non-zero exit, which blocks every push to that repository until someone notices. Treat it as production code on a critical path: keep it small, bound its work, prefer allowing on conditions it does not understand, and keep a fast way to disable it.
Letting every tenant install a pre-receive hook is like letting every tenant in an office block weld their own custom lock onto the shared front door: it works beautifully for whoever fitted it, right up until it jams and nobody can get in.
saying these in an interview costs you the question
- Describing a pre-receive hook as configuration rather than code
- Ignoring that every push waits for the hook to finish
- Missing that a crashing hook blocks all pushes
- Claiming client-side checks could substitute for receiving-side ones
- Asserting specific provider features rather than reasoning about the mechanism