Git And Collaboration Workflows
Shared Local Hook Workflows
A team needs a repeatable way to install and update local hooks without copying opaque scripts into every clone by hand.
Why This Matters
The useful asset is the committed quality command, not one developer's .git/hooks directory. Hook managers can improve installation and cross-platform behavior while leaving CI as the authority.
Working Model
The repository commits scripts and configuration. Installation points Git or a hook manager at those files. Composer scripts provide stable project commands, while tools such as CaptainHook, GrumPHP, pre-commit, and Lefthook manage invocation.
Practical Rules
- Expose checks through Composer or repository scripts first.
- Pin hook-manager versions through the project's dependency mechanism.
- Document one installation and one bypass/recovery path.
- Keep developer-specific optional checks separate from mandatory shared checks.
- Design hooks for Linux, macOS, Windows, containers, and partial staging where supported.
Failure Modes
- Committing generated vendor binaries or machine-specific absolute paths.
- Installing hooks silently during unrelated Composer operations.
- Making local success differ from CI because commands use different flags.
- Blocking emergency work without documenting the controlled bypass and remote safeguards.
Verification
- Test installation in a fresh clone.
- Compare hook commands with CI commands.
- Upgrade the manager in a branch and verify existing clones receive clear instructions.
- Confirm failures are understandable without knowledge of the manager internals.
What You Should Be Able To Do
After this lesson, you should be able to explain how to distribute local checks through versioned scripts, Composer, core.hooksPath, or a hook manager, choose a suitable approach for a real PHP project, and verify the result instead of relying on assumptions.
Shared Local Hooks Need A Product Owner
A shared hook workflow is part of the developer experience. It should have an owner, an installation path, a versioning approach, and a way to report problems. Without ownership, hooks become mysterious local scripts that some developers have, some bypass, and nobody updates safely.
Start by deciding what local hooks are responsible for. Good candidates are fast checks that prevent common mistakes: formatting staged files, rejecting conflict markers, checking commit messages, linting changed PHP, running a targeted unit test, or scanning for obvious secrets. Poor candidates are slow integration tests, deployment approvals, and checks that require production-like services.
Use One Command Per Check
A hook should call the same project command a developer or CI can run manually. For PHP projects, Composer scripts are a common home:
{
"scripts": {
"lint:staged": "php tools/lint-staged.php",
"test:quick": "phpunit --testsuite unit"
}
}
The hook becomes a thin wrapper. When it fails, the developer can run the underlying command outside Git, inspect output, and fix it. CI can also run the command directly. This prevents hook behavior from becoming an untestable side channel.
Installation Models
There are several reasonable approaches. core.hooksPath points Git at a repository directory containing hook scripts. Hook managers can install and update scripts. A setup command can copy or link hooks into .git/hooks. Each model needs documentation and a way to update existing clones.
core.hooksPath is explicit but changes local Git configuration. Copying hooks is simple but can drift when scripts change. Hook managers add a dependency but may solve cross-platform concerns. Choose based on team size and tooling tolerance, then make the install step part of onboarding.
Do not assume hooks are installed merely because scripts are committed. Add a diagnostic command such as composer hooks:doctor or a README section showing how to verify active hooks. Developers should not discover missing hooks only after CI rejects their pull request.
Cross-Platform Concerns
Teams often mix macOS, Linux, Windows, containers, and IDE Git integrations. Shell scripts, path separators, executable bits, line endings, and available binaries can differ. If a hook is required, test it under the supported environments. If Windows support matters, consider using PHP, Node, or another project runtime instead of POSIX-only shell.
Avoid assuming the current working directory. Hooks can be invoked by GUI tools from surprising contexts. Resolve the repository root with Git and use absolute paths where necessary. Quote filenames and handle spaces. Deleted files, renamed files, and partially staged files should not break the hook.
Partial Staging And Formatting
Formatting staged files is convenient but subtle. A developer may stage part of a file while leaving other edits unstaged. If the formatter rewrites the whole file and stages it, the commit can include work the developer did not intend. Hook tools need a strategy: reject partial staging, format only the staged content, or clearly ask the developer to restage after formatting.
For small teams, the simplest policy may be: format files manually before staging, and have the pre-commit hook fail if formatting is needed. For larger teams, a staged-file formatter can be worthwhile, but it must be tested against partial staging.
Local And Remote Responsibilities
Local hooks optimize feedback. Remote checks enforce shared policy. If a local hook runs PHPStan quickly on changed files, CI should still run the authoritative static analysis configuration. If a local hook checks commit messages, the server or pull-request workflow should enforce required release metadata when it matters.
This split lets developers bypass local hooks during emergencies without weakening the repository. The pull request still has to pass remote gates. The bypass should be visible when it matters: ask authors to mention skipped local checks in the pull request if the skip affects confidence.
Updating Hook Workflows
Treat hook changes like tooling changes. Announce them, provide a migration command, and keep old clones in mind. A hook that suddenly requires a new binary will block commits until developers update. Make error messages explain the fix.
When a hook becomes noisy, measure why. Is it slow? Is it checking too much? Is it failing on generated files? Is it inconsistent with CI? A hook that routinely blocks correct work should be fixed or removed. Trust is part of the tool.
Practical Rollout
Roll out one hook at a time. Start with a non-controversial check such as conflict markers or staged PHP syntax. Document installation, test it across platforms, and make CI run the same underlying command. After the team trusts the mechanism, add formatting, secret scanning, or targeted tests.
Keep an escape hatch but review its use. --no-verify is acceptable for a local emergency; it is not a substitute for fixing broken tooling. If bypassing becomes common, the workflow is telling you it does not fit real development.
After this lesson, you should be able to design shared local hooks as maintained developer tooling, choose an installation model, handle cross-platform and partial-staging issues, split local feedback from remote enforcement, and roll out hook changes without surprising the team.
Versioning The Hook Workflow
A hook workflow changes over time. Treat those changes like any other developer-tooling change. Put scripts under version control, review updates, and include a short changelog entry when a hook begins blocking new cases. If a hook manager has its own lock file or configuration, commit it according to the tool's guidance.
Use a doctor command to compare expected and actual setup. It can verify Git configuration, executable scripts, Composer dependencies, runtime versions, and whether the hook path points at the repository-managed directory. This gives support a precise diagnostic instead of asking developers to inspect hidden .git internals by hand.
For teams using containers, decide whether hooks run on the host or inside the development container. Host hooks are fast but depend on host tools. Container hooks are consistent but may be slower. Either choice is valid when documented and tested.
Policy And Support
Document the hook workflow in the repository, not in a chat message. Include installation, update, verification, bypass policy, troubleshooting, and the relationship to CI. New developers should know whether hooks are required, recommended, or optional.
When a hook fails, support should start with reproducibility. Can the developer run the underlying Composer script? Is the hook path configured? Are dependencies installed? Is the failure specific to partial staging or a GUI client? A good workflow makes those questions easy to answer.
If a hook uses a third-party manager, pin its version or installation method. A hook workflow that changes whenever a global tool updates will eventually break at the worst time.
Measuring Developer Impact
Track whether hooks are helping. Useful signals include average hook duration, common failure reasons, bypass frequency, onboarding questions, and CI failures that the local workflow should have caught. If the hook adds delay without reducing remote failures or review noise, simplify it.
Ask for feedback after rollout. A hook that works in one maintainer's shell may fail through an IDE, inside a container, or on Windows. Treat those reports as workflow defects. A shared hook workflow is successful when a new developer can install it, understand its failures, and reproduce its commands without private help. If setup depends on tribal knowledge, write the missing documentation before adding another check. Schedule a periodic review so stale hooks are removed instead of bypassed indefinitely. Keep the owner visible. Review the workflow after onboarding feedback.
Practice
Practice: Choose A Hook Manager
Compare a Composer-native PHP hook manager with a language-neutral manager for a mixed PHP and JavaScript repository.
Your answer must:
- state the intended outcome;
- show the commands, data flow, or implementation shape;
- identify at least one unsafe alternative;
- explain how the result will be verified.
Show solution
Choose based on the team's installed runtimes, staged-file support, configuration clarity, and CI parity. A PHP-native tool fits a PHP-only repository; a language-neutral tool may reduce duplicated setup in a mixed repository.
The important part is not memorising one command or vendor screen. The solution makes the invariant, failure behavior, and verification evidence explicit.
Practice: Design Fresh-Clone Installation
Write the expected setup flow from clone to working hooks.
Your answer must:
- state the intended outcome;
- show the commands, data flow, or implementation shape;
- identify at least one unsafe alternative;
- explain how the result will be verified.
Show solution
Install pinned dependencies, run one explicit project setup command that configures the hook path or manager, execute a diagnostic command, and trigger a known failing fixture. Do not depend on undocumented global tools.
The important part is not memorising one command or vendor screen. The solution makes the invariant, failure behavior, and verification evidence explicit.
Practice: Keep Hooks Fast
A pre-commit hook has grown to eight minutes. Redesign the feedback ladder.
Your answer must:
- state the intended outcome;
- show the commands, data flow, or implementation shape;
- identify at least one unsafe alternative;
- explain how the result will be verified.
Show solution
Run staged syntax and formatting checks at commit, focused tests or static analysis at push, and the complete matrix in CI. Cache safely where supported and let developers run the full command explicitly before requesting review.
The important part is not memorising one command or vendor screen. The solution makes the invariant, failure behavior, and verification evidence explicit.