Technical Evaluation
First, resolve what we are actually evaluating. The supplied material is internally contradictory. The repository (mpociot/captainhook, 2016, 334 stars) and the prior assessment describe a Git hook manager — versioned PHP config mapping pre-commit/commit-msg/pre-push to shell commands, installed into .git/hooks. The description.md describes an entirely different product: outbound webhook delivery (Eloquent subscriptions, queued JSON POSTs, delivery logs). These have different owners, different risk profiles, and different value. One hour reading composer.json, src/, and the README in the actual repo settles it. Do not scope, estimate, or socialize this package until that is done.
Architecture fit
- Git-hooks variant: sits in the developer-tooling layer, not the runtime. Zero production footprint, no data model, no network surface. Its only architectural claim is moving hook definitions out of unversioned
.git/hooks and into versioned config — consistency, not capability.
- Outbound-webhook variant: sits on Laravel's event dispatcher + queue + Eloquent. It is a thin reference implementation of the "model event → queued job → HTTP POST" pattern, not a webhook platform. Everything a platform needs (signing, retries, dead-lettering, filtering, self-service endpoints) is out of scope.
- In both readings, this is a pattern and a prototype, not an architectural commitment.
Integration feasibility
- Git-hooks: low.
composer require, publish config, install the generated scripts, verify by cat .git/hooks/pre-commit. Half a day to first working guardrail. Verify the actual Artisan verb in source — documentation is from 2016 and inconsistent.
- Outbound-webhooks: publish config and migrations, attach the trait to a model, register a URL, deliver through a real queue driver (
database/redis, never sync), and observe against a request bin. Days for a working pilot — if Composer resolves.
Technical risk
- Abandonment is the headline risk. Last release December 2016, zero Packagist dependents, no upstream triage, no security patching. Treat it as ~200 lines of inherited code, not a dependency.
- Version drift is a probable blocker. Expect
illuminate/* constraints, Guzzle/HTTP client drift, and event-dispatcher/container incompatibilities on Laravel 8+/PHP 8. Budget a rewrite, not an upgrade — and do not burn a day on --ignore-platform-reqs to avoid that conclusion.
- Delivery guarantees are absent in the webhook reading. No retries with backoff, no dead-lettering, no auto-disable on repeated failure, no idempotency key, no rate limiting, no payload signing. That is beta-grade, not SLA-grade.
- Security surface differs by reading. Git hooks: audit how command strings are constructed before anything user-influenced can reach the config. Webhooks: unsigned payloads mean spoofing; customer-supplied URLs mean SSRF (block link-local/private ranges); secrets live in committed config unless pulled from env.
- Direction check: the webhook reading is outbound-only. If the requirement is receiving third-party webhooks, this package is irrelevant to the problem.
Key questions to answer before committing
- Which package is it — Git hooks or outbound webhooks? (Blocks everything else.)
- What Laravel/PHP version is the target? Does Composer actually resolve, or are we forking on day one?
- Is delivery contractual (SLA, audit, replay) or best-effort/beta?
- Who supplies subscriber URLs — trusted first-party/design partners, or customers?
- Outbound, inbound, or both?
- Is the customer-facing event taxonomy (
created only, vs. updated/deleted/domain events) already decided? This is permanent API surface and the expensive thing to get wrong.
- Do we need a self-service endpoint UI? It is usually the bulk of the engineering, not the delivery code.
Integration Approach
Stack fit
- Git-hooks: orthogonal to the application — no production coupling, no deploy implications, safe to trial on one team. It fits any PHP repo in practice, even though the package is Laravel-wired.
- Outbound-webhooks: a natural fit for a Laravel shop already using events and queues. Keep it behind a feature flag and an internal adapter interface so the delivery mechanism is swappable when reliability requirements arrive.
Migration path — vendor it, do not depend on it
Given the December 2016 release and zero dependents, the correct integration is copy, not require. Move the code into app/ or a private package you control, pin it, and accept you will patch it. An abandoned transitive dependency in composer.json becomes a tax on every future framework upgrade, and that negotiation is where it will eventually be ripped out under duress. If it must stay a Composer dependency for the spike, isolate it to a throwaway branch.
Compatibility
- Assume the package targets the Laravel 5.x / PHP 5.6–7.x era. Validate the constraint set in
composer.json before writing a ticket.
- For the webhook reading, explicitly test the HTTP client and queue payload serialization — both are the usual drift points across major Laravel versions.
- Windows teams: shell hooks with CRLF endings fail with
bad interpreter. Resolve before rollout; it is the most common "the hook doesn't work" report.
Sequencing
- Resolve identity (1 hour, blocking). Read source, not docs.
- Time-boxed spike — one engineer-week, hard stop, behind a flag. Success criteria written down before it starts.
- If Git-hooks:
pre-commit runs fast lint/style on staged files only (sub-5s) → validate the failure path actually blocks a commit → add commit-msg regex validation → add pre-push full suite → mirror all of it in CI → automate install in composer.json post-install and add to the onboarding checklist.
- If outbound-webhooks: delivery log first (without it you cannot debug a customer ticket) → event taxonomy decision → signature + retries with backoff + dead-lettering → per-endpoint filtering and rate limits → self-service endpoint UI last, because it is the largest chunk and only justified by adoption data.
- Rewrite decision gate: if the spike shows Composer conflicts or missing guarantees the business needs, stop and build in-house using the source as an architecture reference — it is a readable template and a legitimate input to an internal estimate.
Decision framing for stakeholders: ship the thin version to design partners, instrument registration rate / event-type mix / delivery success rate, and let that data order the roadmap: prove demand → invest in delivery guarantees → invest in developer self-service.
Operational Impact
Maintenance
- Ownership transfers to us on day one. No upstream fixes, no security patches, no issue triage. Budget ongoing small-but-real maintenance rather than zero.
- The Git-hooks reading is genuinely low-maintenance: config file, occasional path fixes, reinstall on new clones.
- The webhook reading carries a standing obligation — every Laravel upgrade is a compatibility event we own.
Support
- Git-hooks: support load is onboarding, not production. The recurring tickets are "my hook doesn't fire" (cwd, line endings, install not run) and "the hook was bypassed." A sanctioned, documented skip path (
--no-verify, or an env var the config reads) is better than developers disabling hooks permanently and silently.
- Outbound-webhooks: support load is customer-facing and will exist whether or not the tooling does. "We didn't receive the event" is unanswerable without a per-delivery log with request, response, status, and timestamp. Build the log before promising anything externally, and plan for manual replay as a support workflow — not a feature, a necessity.
Scaling
- Git-hooks: scales with repo size and test-suite duration. A 30-second
pre-commit guarantees --no-verify within a week, and a disabled hook is worse than no hook because the team believes it is running. Scope tools to staged files; push the expensive gate to pre-push.
- Outbound-webhooks: scales with subscriber count × event volume × retry attempts. Slow endpoints hold queue workers — enforce timeouts. Fan-out multiplies on retries and can produce thundering-herd behavior with no jitter or backoff. Per-subscriber rate limits are eventually required, and this package has none.
Failure modes to design against
- Silent success. A command that prints errors but exits
0 never blocks the commit; a delivery with no log looks identical to one that never happened. Both failures are invisible by default. Test the failure path explicitly in both readings.
- Bypass. Hooks are skippable by
--no-verify, IDE integrations, and rebase merges. CI must be authoritative; hooks are fast feedback only. Keep the commands identical in both places so a CI failure reproduces locally.
- Path and working-directory assumptions. Relative paths like
vendor/bin/pint fail silently if the cwd is .git/hooks rather than the repo root. Use git rev-parse --show-toplevel or verify $PWD.
- Queue backlog / event loss. No dead-lettering means permanent, unrecorded loss under load or endpoint outages.
- Security debt. SSRF via customer URLs, spoofable unsigned payloads, secrets committed in config. Confirm each control exists before any external commitment; assume it does not.
- Upgrade cliff. The package resolving today does not mean it resolves on the next major Laravel. Vendoring converts this from a surprise into a scheduled task.
Ramp-up
- An engineer fluent in Laravel events and queues needs an hour with the source and roughly a day to a working prototype. The source is short enough to read end to end — do that rather than trusting the 2016 README.
- Team-wide ramp-up is process, not training: an onboarding checklist item that verifies hooks after a fresh clone (clone into a temp directory and commit without manual setup), and a CI job that produces the same result.
- Set expectations explicitly: this buys speed to a validated decision, not durability. Anything customer-facing with an SLA attached is a follow-on build, and the pilot's job is to tell us how big that build needs to be.