Database & Telemetry
Hassette stores operational telemetry in a local SQLite database. This data powers the Apps page stats strip, handler invocations, job executions, and app health metrics in the web UI.
What Is Collected
Hassette records two types of telemetry:
- Handler invocations — every time an event bus listener fires, the database records the start time, duration, success/failure, and any error details.
- Job executions — every time a scheduled job runs, the same metrics are recorded.
Telemetry is collected automatically. You do not need to enable it or write any code — it works out of the box.
Source Tier
Framework-internal handlers (telemetry workers, WebSocket service, scheduler services) are included in Apps page stats strip counts alongside your app registrations. Framework errors appear in the unified Error Spotlight with a Framework badge and the component name (e.g. Service Watcher, App Handler), so you can distinguish them from your app errors at a glance. The Handler health grid shows only your apps — framework components do not appear there.
Internal detail
Internally, framework handlers are stored with source_tier='framework' and a component-specific app_key of the form __hassette__.<component> — for example __hassette__.service_watcher, __hassette__.app_handler, or __hassette__.core. This naming identifies which part of the framework produced an error and is used by the web UI to display the component name in the Framework badge. The Handler health grid filters out all framework keys, while the stats strip and Error Spotlight include all tiers by default.
Execution Columns
Handler invocations and job executions are stored together in a single executions table. A kind column ('handler' or 'job') distinguishes the two. Handler rows carry a listener_id foreign key; job rows carry a job_id foreign key. Exactly one of the two is non-null per row.
The following columns are present on every row regardless of kind.
| Column | Type | Description |
|---|---|---|
kind |
string | 'handler' for bus listener invocations; 'job' for scheduled job executions |
listener_id |
integer | null | Foreign key into listeners. Set for handler rows; null for job rows. |
job_id |
integer | null | Foreign key into scheduled_jobs. Set for job rows; null for handler rows. |
execution_start_ts |
float | Unix timestamp when execution started |
duration_ms |
float | Wall-clock time in milliseconds |
status |
string | Outcome: success, error, cancelled, or timed_out |
is_di_failure |
boolean | Whether the execution failed due to a dependency injection error (handler rows only; always 0 for job rows) |
source_tier |
string | app for user automations, framework for internal Hassette components |
error_type |
string | null | Exception class name, if execution raised an error |
error_message |
string | null | Exception message, if execution raised an error |
error_traceback |
string | null | Full Python traceback, if execution raised an error |
execution_id |
string | null | UUID tying this row to a specific trigger delivery or scheduler invocation. null for rows written before this feature was added. |
trigger_context_id |
string | null | UUID identifying the event that triggered a handler. For HA events, this is context.id from the originating Home Assistant event and is stable across all handlers receiving the same event. For Hassette-internal events, unique per firing. null for job rows and for rows written before this feature was added. |
trigger_origin |
string | null | Where the trigger originated: LOCAL, REMOTE, or HASSETTE. null for job rows and for rows written before this feature was added. |
Pre-migration rows
The execution_id, trigger_context_id, and trigger_origin columns were added in a schema migration. Rows written before this migration have null in all three columns. The web UI renders null as "—" in the Trace ID, Trigger, and Origin columns.
Configuration
All database settings are optional. The defaults work well out of the box.
# hassette.toml
[hassette.database]
path = "/path/to/hassette.db" # default: {data_dir}/hassette.db
retention_days = 7 # default: 7 (minimum: 1)
max_size_mb = 500 # default: 500 (0 = no size limit)
| Field | Type | Default | Description |
|---|---|---|---|
path |
path or null | null |
Location of the SQLite database file. When null (default), Hassette stores the database at {data_dir}/hassette.db. |
retention_days |
integer | 7 |
How many days of handler invocation and job execution records to keep. Records older than this are automatically deleted. Minimum: 1 day. |
max_size_mb |
float | 500 |
Maximum total database file size in megabytes. When exceeded, Hassette deletes the oldest execution records to reclaim space. Set to 0 to disable the size limit. |
How Retention Works
Hassette runs two automatic maintenance routines:
- Time-based retention — every hour, records older than
retention_daysare deleted from theexecutionstable. Internal bookkeeping records (session tracking) are not affected by retention cleanup. - Size-based failsafe — every hour, if the total database size (including WAL files) exceeds
max_size_mb, the oldest execution records are deleted in batches until the database is back under the limit.
Both routines run in the background and do not block normal operation.
Monitoring Telemetry Health
/api/telemetry/status
This endpoint checks whether the telemetry database is healthy and responding to queries.
| Response | Status Code | Meaning |
|---|---|---|
{"degraded": false} |
200 | Database is healthy |
{"degraded": true} |
503 | Database is unavailable |
Use this endpoint if you want to monitor specifically whether telemetry data collection is working. When degraded, the web UI continues to function but shows zeroed-out metrics.
/api/health
This is the system-level status view for Hassette as a whole. It reports whether the WebSocket connection to Home Assistant is up, starting, or disconnected. The endpoint returns HTTP 200 in all states while the process can serve — it never returns 503 from the handler itself.
status body field |
HTTP | Meaning |
|---|---|---|
ok |
200 | WebSocket currently connected (per-service health is in the services field) |
degraded |
200 | Was connected at least once; currently disconnected (e.g. HA restarting) |
starting |
200 | Has not finished the initial connection yet |
A fatal crash (a PERMANENT service exhausting its restart budget, or a startup failure) records a failure status to the current telemetry session before Hassette exits with a non-zero exit code. A clean operator shutdown (SIGTERM / docker stop) exits 0.
For container restart automation, use /api/health/live or rely on the non-zero exit and a restart policy. Use /api/health for the human-readable aggregate view and use /api/health/ready for load-balancer routing. See Health Endpoints for the full reference.
Choosing the right endpoint
Use /api/health/live (or the non-zero exit + restart policy) for restart automation. Use /api/health/ready for traffic routing. Use /api/health for the aggregate human view. Use /api/telemetry/status to monitor specifically whether the telemetry database is functional.
Registration Persistence
Listener and job registrations are stored in the database and survive restarts. When Hassette starts, existing registrations are matched by their natural key (handler name, topic, and predicate signature — or the explicit name= value) and updated in place via upsert semantics. Registrations that no longer exist in the new session are marked with a retired_at timestamp rather than deleted.
This means the Apps page stats strip shows accurate handler and job counts even for registrations that predate the current startup. Historical registrations from prior startups remain visible in the web UI until they age out of the retention window.
Degraded Mode
When the telemetry database becomes unavailable (disk full, permissions error, corruption), Hassette enters degraded mode:
- The web UI continues to work, but telemetry-backed panels (stats strip, Error Spotlight, handler/job metrics) show empty or zeroed-out data.
- The status bar shows a degraded indicator to alert you.
- Your automations continue to run normally — telemetry is an observability layer, not a dependency for app execution.
- All telemetry is unavailable — including persisted registrations. Because registration data lives in the same SQLite file, handler and job counts will also show zero until the database recovers.
Recovery
To resolve a degraded state:
- Check disk space — in Docker:
docker compose exec hassette df -h /data - Check file permissions — ensure the Hassette user can write to the database path
-
Delete and restart — if the database is corrupted, deleting it is safe. Only telemetry history is lost; your automations are unaffected:
# Docker docker compose exec hassette rm /data/hassette.db docker compose restart hassette # Local (replace v0 with your major version, or use your configured database.path) rm ~/.local/share/hassette/v0/hassette.dbHassette will recreate the database on next startup.
Related Resources
- Global Configuration — all configuration fields
- App Cache — the disk cache for app data (separate from telemetry)