darkwood/ia-exception-bundle
Symfony bundle that replaces raw HTTP 500 stack traces with LLM-generated explanations, probable causes, and suggested fixes via Symfony AI. Configurable status codes, agent, timeout, cache, and async non-blocking analysis for readable, production-safe error pages.
Architecture fit. This is a Symfony bundle, not a framework-agnostic PHP library. It attaches at kernel.exception, resolves an agent service ID (ai.agent.default) from the Symfony AI container, renders through a Twig error template, and registers config under a darkwood_ia_exception tree in YAML. Laravel has no equivalent extension points: no bundles, no kernel.exception, no Twig-by-default, no service-ID-based agent resolution. Architectural fit for a Laravel codebase is effectively zero for direct installation. The pattern — intercept 5xx, fingerprint and cache, call an LLM with sanitized exception context, degrade silently on failure — fits Laravel fine and is implementable in bootstrap/app.php + withExceptions() plus a queued job.
Integration feasibility. Three viable routes, in ascending order of ownership:
| Option | Mechanism | Effort | Fit |
|---|---|---|---|
| A. Install as-is | Requires Symfony 8.x app | N/A for Laravel | None — blocked |
| B. Sidecar Symfony service | Standalone Symfony 8 app hosting this bundle; Laravel forwards exception payloads | 2–4 weeks infra + auth + network | Medium |
| C. Port the pattern into a Laravel package | Exception handler hook, queue job for async, PSR-6/Cache:: fingerprint cache, Blade partial |
1–2 weeks eng | High |
| D. Fork the bundle | Replace listener with Laravel handler | 1–2 weeks + permanent fork maintenance | Low (you inherit Symfony coupling) |
The bundle's own logic surface is small — a listener, a prompt builder, a cache fingerprint, one async controller. Option C is realistic. The expensive dependency is not this package but symfony/ai-bundle, which the changelog shows moved through ^0.6 → ^0.8 → ^0.10 → ^0.12 within a few months. That pre-1.0 churn, not the exception bundle, is the schedule risk.
Technical risk.
async: false) blocks kernel.exception and directly extends user-facing 500 latency.GET /__ai_exception/{error_id} returns analysis of a real production error. It ships unauthenticated by default; it needs auth, rate limiting, and a documented retention policy for async_context_ttl.Key questions.
Stack fit. Not installable on Laravel. Treat this repository as a specification and reference implementation rather than a dependency. The valuable, portable pieces are: the fingerprint-based cache key (exception class + message + top frames), the fail-safe contract ("never set a response if AI fails"), the sanitization allowlist (class/message/file/line only), and the async-with-graceful-fallback UX.
Migration path. Do not attempt coexistence. If a spike is needed, stand up a throwaway Symfony 8.1 app in a sandbox to observe real output quality on 20–30 representative production exceptions before writing any Laravel code. That test answers the only question that matters — is the analysis actually useful — independent of framework.
Compatibility. Hard blockers if installing: PHP 8.5, Symfony 8.x, symfony/ai-bundle ^0.12, and a configured platform + agent with a scoped HTTP client to enforce timeout_ms. Versioning is confusing: the project uses unified Darkwood versioning at 1.3.0 and then jumps to 8.x to track Symfony patch releases, so constraint management is a manual exercise. Expect upgrade pressure every Symfony patch.
Sequencing.
withExceptions hook, allowlisted context extractor, cache key, queue-based async, auth + rate limit on any retrieval endpoint, vendor DPA.include_trace permanently false, hard concurrency and spend caps, circuit breaker to a plain 500 page.Maintenance. Upstream is a satellite of a monorepo and inherits its release cadence (8.1.0 → 8.1.1 → 8.1.2 → 8.1.3 in short succession). Consuming it means tracking Symfony patch releases and pre-1.0 AI SDK breaking changes. Porting (Option C) trades that for owning ~500 lines of code plus the LLM integration — usually the better trade; the AI SDK integration is the part we would have to own anyway.
Support. No dependents and a single maintainer under MIT mean no support contract and no community troubleshooting corpus. Budget for the first adopter's tax: our own debugging, our own runbooks. If the maintainer stops, the fork cost is small but non-zero, and there is no CVE-watch coverage for a package this obscure.
Scaling. Cost and latency scale linearly with novel error volume. Caching by fingerprint collapses repeated errors (default 600s TTL) but provides no help during a first-of-its-kind incident, when cache hit rate is ~0% and every distinct error fans out to a separate LLM call. Mitigations must be first-class: queue with capped workers, per-minute spend ceiling, circuit breaker that disables enrichment after N consecutive failures or budget exhaustion.
Failure modes and required behavior.
| Failure | Expected behavior | Must verify |
|---|---|---|
| LLM timeout | Default 500 page renders | Confirmed by design; verify in our port |
| LLM returns malformed JSON | Page still renders | Not documented — test explicitly |
| Route not imported (async) | Silent placeholder stuck | Documented gotcha; prefer sync or explicit auth route |
| Cache backend down | Falls back to live call, cost spike | Needs circuit breaker |
| Budget exceeded | Hard stop, no enrichment | Not provided; must build |
| Sensitive data in message | Leaked to vendor | Requires pre-send scrubber, not just include_trace: false |
| Prompt injection via message | Manipulated output | Acceptable only if developer-facing |
Ramp-up. Reading and configuring the bundle is roughly half a day; the real cost is the surrounding platform work — secret management for the LLM key, scoped HTTP client with enforced timeouts, vendor onboarding and DPA, and on-call training to treat generated output as a labeled hypothesis rather than a diagnosis. Plan for one engineer-week of enablement plus an explicit "do not trust, verify" convention in the incident runbook.
Recommendation. Do not adopt as a dependency in a Laravel estate — the framework and PHP-version constraints make it non-viable, and 0 dependents/2 stars means no external validation. Fund a one-week sandbox spike to test whether LLM error diagnosis actually reduces MTTR on our own exception corpus, and if it does, build a Laravel-native equivalent reusing the bundle's fail-safe contract, sanitization allowlist, and fingerprint caching. Gate production rollout on a measured MTTR delta, a hard spend ceiling, and a completed vendor data review.
How can I help you explore Laravel packages today?