PHPackages                             fissible/fault - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Logging &amp; Monitoring](/categories/logging)
4. /
5. fissible/fault

ActiveLibrary[Logging &amp; Monitoring](/categories/logging)

fissible/fault
==============

Exception tracking and triage for fissible/watch cockpit

v1.1.1(1mo ago)07MITPHPPHP ^8.2CI passing

Since Mar 25Pushed 1mo agoCompare

[ Source](https://github.com/fissible/fault)[ Packagist](https://packagist.org/packages/fissible/fault)[ Docs](https://github.com/fissible/fault)[ RSS](/packages/fissible-fault/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (5)Dependencies (9)Versions (6)Used By (0)

fissible/fault
==============

[](#fissiblefault)

Exception tracking and triage for the [fissible/watch](https://github.com/fissible/watch) cockpit. Captures exceptions via the Laravel exception handler, deduplicates them by fingerprint, and surfaces them at `/watch/faults` with status management, developer notes, and regression test generation.

**Depends on:** fissible/watch (uses the cockpit layout and route prefix config).

```
  [forge]  ──────────────────────────────►  [accord]  ◄── [watch] ◄── [fault]  ← you are here
  generate / update spec                   validate at      cockpit UI   exception tracking
      ▲                                    runtime │
      │                                            ▼
      └──────────────────────────────────  [drift]
                                           detect drift, bump version

```

---

What it does
------------

[](#what-it-does)

FeatureDescription**Capture**Hooks into Laravel's `withExceptions()` to record every unhandled exception**Deduplication**Groups exceptions by fingerprint: SHA-256 of `class|relative_file|line` — same bug, one group regardless of message variation**Triage UI**Filterable, paginated list at `/watch/faults` with open / resolved / ignored status**Detail view**Full meta, sample stack trace, occurrence count, first/last seen timestamps**Notes**Auto-saving textarea for AI evaluations, root-cause analysis, or developer comments**Status workflow**Mark as resolved, ignored, or reopen — with optional reopen-on-recurrence**Test generation**One-click PHPUnit skeleton annotated with `@group fault-{hash}`; passing the test signals the fix is complete**Configurable ignore list**Skip exceptions that are expected (404s, auth errors, validation errors)---

Installation
------------

[](#installation)

```
composer require fissible/fault
```

The service provider registers automatically via Laravel's package discovery. Run the migration to create the `watch_fault_groups` table:

```
php artisan migrate
```

---

Handler integration
-------------------

[](#handler-integration)

Wire fault into your application's exception handler in `bootstrap/app.php`:

```
->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->report(function (Throwable $e): void {
        app(\Fissible\Fault\Services\FaultReporter::class)->capture($e);
    });
})
```

This is the only change needed. `FaultReporter::capture()` is a no-op when `FAULT_ENABLED=false`, when the exception matches the ignore list, or when the max group cap has been reached.

---

Configuration
-------------

[](#configuration)

```
FAULT_ENABLED=true               # set false to disable capture entirely (e.g. in CI)
FAULT_MAX_GROUPS=500             # cap on distinct fault groups (0 = unlimited)
FAULT_REOPEN_ON_RECURRENCE=true  # reopen resolved/ignored faults when they fire again
FAULT_CONTEXT_DEPTH=10           # stack frames captured in sample_context
```

Publish the config to customise the ignore list or other defaults:

```
php artisan vendor:publish --tag=fault-config
```

`config/fault.php` ships with a sensible default ignore list covering Laravel's built-in HTTP and auth exceptions:

```
'ignore' => [
    \Illuminate\Auth\AuthenticationException::class,
    \Illuminate\Auth\Access\AuthorizationException::class,
    \Illuminate\Database\Eloquent\ModelNotFoundException::class,
    \Illuminate\Http\Exceptions\ThrottleRequestsException::class,
    \Illuminate\Session\TokenMismatchException::class,
    \Illuminate\Validation\ValidationException::class,
    \Symfony\Component\HttpKernel\Exception\HttpException::class,
    \Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class,
],
```

---

How fingerprinting works
------------------------

[](#how-fingerprinting-works)

Each exception is fingerprinted using a SHA-256 hash of three stable fields:

```
sha256("{exception_class}|{relative_file}|{line}")

```

The **message is intentionally excluded** from the fingerprint. Variable-content errors (database IDs, user input, timestamps in messages) all map to the same group — so you see "this class of bug has occurred 47 times" rather than 47 separate entries.

When an exception fires:

- If no group exists for the hash, a new one is created (subject to `max_groups` cap)
- If a group exists, `occurrence_count` and `last_seen_at` are updated
- If `reopen_on_recurrence` is true and the group is resolved or ignored, it is set back to `open`

---

Fault groups
------------

[](#fault-groups)

The `watch_fault_groups` table stores one row per unique fingerprint:

ColumnDescription`group_hash`SHA-256 fingerprint (unique)`class_name`Fully-qualified exception class`message`Exception message (truncated to 500 chars)`file`Relative path from app base`line`Line number`occurrence_count`Total number of times this fault has fired`first_seen_at` / `last_seen_at`Timestamps`status``open` | `resolved` | `ignored``resolution_notes`Free-text developer notes (AI evals, root-cause, links)`resolved_at`When the fault was last resolved`app_version`Optional — pass via `FaultReporter::capture($e, $version)``sample_context`JSON stack trace from first capture`generated_test`PHPUnit skeleton (set via "Generate Test" in the UI)---

FaultGroup model API
--------------------

[](#faultgroup-model-api)

```
use Fissible\Fault\Models\FaultGroup;

$group = FaultGroup::where('status', 'open')->latest('last_seen_at')->get();

$group->isOpen();      // bool
$group->isResolved();  // bool
$group->isIgnored();   // bool

$group->markResolved('Fixed in PR #123');
$group->markIgnored('Expected during maintenance windows');
$group->reopen();

$group->shortClass();       // 'QueryException' (last segment of FQCN)
$group->relativeFile();     // 'app/Services/Foo.php'
$group->statusBadgeClass(); // 'badge-open' | 'badge-resolved' | 'badge-ignored'
```

---

Test generation
---------------

[](#test-generation)

Clicking **Generate Test** in the fault detail view calls `TestStubGenerator::generate()` and stores the result in `generated_test`. The stub:

- Is a valid PHPUnit class named `{ShortClass}FaultTest`
- Is annotated with `@group fault-{hash_short}` so you can run just that test
- Contains a `markTestIncomplete()` placeholder you replace with a real reproduction

```
php artisan test --filter fault-deadbeef
```

When the test passes, mark the fault group as **resolved** in the UI.

---

Custom error page
-----------------

[](#custom-error-page)

For applications that want to show a branded error page linking back to the fault entry, add a render callback in `bootstrap/app.php`:

```
->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->report(function (Throwable $e): void {
        app(\Fissible\Fault\Services\FaultReporter::class)->capture($e);
    });

    $exceptions->render(function (Throwable $e, Request $request) {
        if ($request->expectsJson()) return null;

        $status = method_exists($e, 'getStatusCode') ? $e->getStatusCode() : 500;
        if (in_array($status, [401, 403, 404, 419])) return null;

        $faultGroup = \Fissible\Fault\Models\FaultGroup::where(
            'group_hash',
            hash('sha256', get_class($e).'|'.Str::after($e->getFile(), base_path('/')).'|'.$e->getLine())
        )->first();

        return response()->view('errors.cockpit', [
            'status'     => $status,
            'title'      => class_basename($e),
            'message'    => $e->getMessage(),
            'trace'      => $e->getTraceAsString(),
            'faultGroup' => $faultGroup,
        ], $status);
    });
})
```

See [Pilot](https://github.com/fissible/pilot) for a working implementation of this pattern.

---

Prerequisites
-------------

[](#prerequisites)

fissible/watch must be installed and configured (routes registered, layout component available) before fault's UI will render correctly. See the [watch README](https://github.com/fissible/watch) for setup instructions.

---

License
-------

[](#license)

MIT

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance89

Actively maintained with recent releases

Popularity4

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~4 days

Total

5

Last Release

58d ago

Major Versions

v0.1.1 → v1.0.02026-03-26

### Community

Maintainers

![](https://www.gravatar.com/avatar/ef298fbffac2225e36ab3a6e089af020f76885bc65d5f6afda831c64939e1e4b?d=identicon)[fissible](/maintainers/fissible)

---

Top Contributors

[![fissible](https://avatars.githubusercontent.com/u/1410914?v=4)](https://github.com/fissible "fissible (28 commits)")

---

Tags

laravelDevtoolsexceptionstrackingwatchtriage

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/fissible-fault/health.svg)

```
[![Health](https://phpackages.com/badges/fissible-fault/health.svg)](https://phpackages.com/packages/fissible-fault)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[laravel/cashier

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

2.5k28.4M134](/packages/laravel-cashier)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k14.1M120](/packages/laravel-pulse)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9732.3M121](/packages/roots-acorn)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

76318.2M110](/packages/laravel-mcp)[laravel/cashier-paddle

Cashier Paddle provides an expressive, fluent interface to Paddle's subscription billing services.

268880.7k3](/packages/laravel-cashier-paddle)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
