Skip to content

fix(cli): bound the doctor state.db health probe with a cancellable SQLite deadline (supersedes #72527) - #76003

Open
briandevans wants to merge 2 commits into
NousResearch:mainfrom
briandevans:fix/cli-doctor-state-db-deadline-72441
Open

fix(cli): bound the doctor state.db health probe with a cancellable SQLite deadline (supersedes #72527)#76003
briandevans wants to merge 2 commits into
NousResearch:mainfrom
briandevans:fix/cli-doctor-state-db-deadline-72441

Conversation

@briandevans

Copy link
Copy Markdown
Contributor

What does this PR do?

Supersedes #72527 (thanks @JonthanaHanh — the diagnosis and the line are right; the boundary is what needs replacing) and closes the hang reported in #72441.

hermes doctor stalls indefinitely right after printing state.db exists (N sessions). The next statement is _db_opens_cleanly(state_db_path) (hermes_cli/doctor.py), whose PRAGMA integrity_check walks every page of the file — O(file size), with no output and no way to interrupt it. The repo already documents this exact pathology in its own test suite:

tests/hermes_cli/test_doctor.py (TestGitHubTokenCheck._isolate_home) — "On a dev machine with a large state.db that meant a multi-minute PRAGMA integrity_check that blew the 300s per-file budget and killed the whole file."

and already bounds the same scan elsewhere: hermes_cli/backup.py:320-353,404-441 caps integrity_check with a byte ceiling "because integrity_check walks every page … O(file size)", falling back to a cheap header/structural probe.

This is built directly to the two changes requested in review on #72527:

  • Use a genuinely cancellable SQLite-level deadline, such as a progress handler that interrupts the integrity scan, rather than a thread timeout alone.
  • Add an elapsed-time regression test for a blocking/over-deadline probe.

Cross-surface reach — one call site, three surfaces, no extra files touched. run_doctor is the shared implementation for the terminal command (hermes_cli/main.py:4827-4829), the Hermes Console doctor verb (hermes_cli/console_engine.py:1279-1281), and the desktop Command Center maintenance op (hermes_cli/web_server.py:12581-12584, POST /api/ops/doctor). A wedged probe left the Command Center's Doctor action reporting running: true forever.

Why the thread timeout in #72527 does not bound the command

Its diff submits the probe to a with concurrent.futures.ThreadPoolExecutor(max_workers=1) as _ex: block. Leaving that block calls Executor.shutdown(wait=True), which blocks until the probe finishes — so the 30 s result() only delays the warning. Measured on this branch, with a probe that blocks for 90 s:

variant probe blocks hermes doctor wall clock warns?
origin/main 90 s 92.4 s no
origin/main + #72527 90 s 92.3 s yes, at 30 s — then keeps waiting
this PR (defaults) 90 s 37.3 s yes, at 30 s, and returns

Switching that to shutdown(wait=False) on a stdlib pool would not fix it either: stdlib workers are registered in concurrent.futures.thread._threads_queues, whose atexit hook joins every worker unconditionally. The hang just moves to interpreter exit. tools/daemon_pool.py's module docstring states this and exists precisely for this case; measured here, a stdlib pool with one abandoned 15 s worker takes 15.08 s to exit the interpreter after shutdown(wait=False), versus 0.04 s for DaemonThreadPoolExecutor.

The data-safety invariant (neither the original PR nor the review covers this)

A progress-handler abort raises sqlite3.OperationalError("interrupted"), and OperationalError is a subclass of sqlite3.DatabaseError. _db_opens_cleanly ends with except sqlite3.DatabaseError as exc: return str(exc), so a naive deadline returns "interrupted" as an unhealthy reason. In doctor.py a non-None reason under --fix calls repair_state_db_schema(), whose escalation path drops the whole messages_fts% schema and VACUUMs. A healthy-but-large state.db would be sent into destructive repair merely for being slow. Verified by building exactly that naive shape locally: repair_state_db_schema was called once, on a healthy database, printing Repaired state.db FTS write health.

The deadline is therefore surfaced as a new DBHealthProbeTimeout, deliberately outside the sqlite3 exception hierarchy so no except sqlite3.DatabaseError handler can swallow it as corruption, and doctor reports the check as skipped rather than passed or failed.

Related Issue

Fixes #72441

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • hermes_state.py
    • _db_opens_cleanly gains a keyword-only deadline_seconds: Optional[float] = None. The default is the historical unbounded contract, so every existing caller is byte-for-byte unaffected.
    • When a deadline is given, a _ProbeDeadline progress handler is installed on the probe connection (1000 VM instructions per callback). Measured: PRAGMA integrity_check fires it 170 times on a 41 MB database and 21 times on a 700 KB one, so the deadline is polled continuously through the scan.
    • New DBHealthProbeTimeout (plain Exception, not a sqlite3 error), raised only when the handler actually aborted a statement — never returned as a reason string.
    • The probe statements move into _run_db_health_probe(conn) so the connection prologue (and any deadline on it) is owned by the caller. The statement bodies are unchanged.
  • hermes_cli/doctor.py
    • _STATE_DB_PROBE_DEADLINE_S = 30.0 (matching fix(cli): add timeout to state.db health probe in hermes doctor (#72441) #72527's chosen budget) and _STATE_DB_PROBE_ABANDON_GRACE_S = 5.0, module constants so tests can monkeypatch them. No config key and no CLI flag — a diagnostic should not need tuning in order to terminate.
    • _run_abandonable(fn, timeout_s): tools.daemon_pool.DaemonThreadPoolExecutor + explicit shutdown(wait=False) in a finally. Never with ThreadPoolExecutor(...). This is the belt-and-braces layer for work SQLite cannot interrupt at all — a connect() or page read wedged in an uninterruptible I/O syscall, e.g. a hung network mount. The grace means the in-statement cancellation is normally what stops the probe (it closes its connection on the way out) and the thread boundary is the fallback.
    • On timeout: check_warn + an entry in issues stating the database was not modified, and the repair branch is skipped entirely (elif), not entered with a None reason.
    • The SELECT COUNT(*) FROM sessions immediately above the probe now runs inside the same bounded region. Exceptions from it still propagate unchanged to the is_malformed_db_error handler, so malformed-schema detection and repair are preserved.
  • tests/hermes_cli/test_doctor_state_db_deadline.py (new file, 6 tests).

Sibling-site sweep

Root cause: a read-only state.db health probe runs unbounded inside an interactive diagnostic. Every site of it, and why:

site disposition
hermes_cli/doctor.py _db_opens_cleanly(state_db_path) covered — the reported hang
hermes_cli/doctor.py SELECT COUNT(*) FROM sessions (immediately above) covered — same block, same concern, same bounded region
hermes_cli/console_engine.py:1279-1281 (doctor console verb) covered for free — delegates to run_doctor; file not touched
hermes_cli/web_server.py:12581-12584 (POST /api/ops/doctor) covered for free — spawns hermes doctor; file not touched
hermes_state.py ×5 inside repair_state_db_schema excluded — they verify whether each repair strategy worked; bounding them would make repair report a false failure
hermes_cli/sessions_cmd.py (hermes sessions repair) excluded — an explicitly requested repair must be allowed to complete
hermes_cli/console_engine.py:1492 (sessions repair verb) excluded — mirror of the above
hermes_cli/session_recovery.py:1009 excluded — post-recovery verification of a disposable copy; must be exhaustive
hermes_cli/doctor.py post---fix re-count and PRAGMA wal_checkpoint(PASSIVE) excludedshould_fix-only; PASSIVE is non-blocking by definition
hermes_cli/backup.py:404-441 excluded, cited — already bounded by a byte ceiling; it is the in-repo precedent that this concern is real
hermes_cli/kanban_db.py excluded — different database, different command (hermes kanban repair), explicit repair path

All excluded call sites pass no deadline_seconds and so keep the exact behaviour they have today.

How to Test

Reproduce the hang and the fix without a multi-gigabyte database, by making the probe block:

  1. On origin/main, monkeypatch hermes_state._db_opens_cleanly to time.sleep(90) and call run_doctor(Namespace(fix=False, ack=None)) against an isolated HERMES_HOME. It returns after 92.4 s with no warning. Apply fix(cli): add timeout to state.db health probe in hermes doctor (#72441) #72527's diff and it returns after 92.3 s, having printed the timeout warning at 30 s. On this branch it returns after 37.3 s.
  2. Real SQLite, no mocks:
    from hermes_state import DBHealthProbeTimeout, _db_opens_cleanly
    _db_opens_cleanly(big_db, deadline_seconds=0)   # raises DBHealthProbeTimeout
    _db_opens_cleanly(big_db)                       # returns None — the DB is healthy
    The same file probing healthy without a deadline is what proves the timeout is a cancellation, not a corruption finding.
  3. uv run --with pytest --with pytest-xdist --with pytest-asyncio python3 -m pytest \
      tests/hermes_cli/test_doctor_state_db_deadline.py tests/hermes_cli/test_doctor.py \
      tests/test_state_db_malformed_repair.py tests/test_hermes_state.py -v
    

Results (per file, matching scripts/run_tests.sh's per-file isolation):

file result
tests/hermes_cli/test_doctor_state_db_deadline.py 6 passed
tests/hermes_cli/test_doctor.py 47 passed
tests/test_state_db_malformed_repair.py 9 passed
tests/test_hermes_state.py 140 passed
tests/hermes_cli/test_doctor_command_install.py 3 passed
tests/hermes_cli/test_session_listing.py 6 passed
tests/test_sqlite_wal_reset_gate.py 19 passed

New tests:

test asserts
test_doctor_returns_without_waiting_for_a_blocked_probe the requested elapsed-time regression: with a probe that blocks 20 s, run_doctor returns in well under half that
test_probe_timeout_is_reported_as_skipped_not_as_corruption the output says the check timed out and the DB was not modified, and never says "FTS index may be corrupt"
test_probe_timeout_does_not_trigger_repair_under_fix with --fix and a timing-out probe, repair_state_db_schema is never called
test_progress_handler_cancels_a_real_integrity_check real SQLite: deadline_seconds=0 raises DBHealthProbeTimeout, the exception is not a sqlite3.Error, and the same file returns None (healthy) with no deadline
test_no_deadline_preserves_existing_behaviour no kwarg: healthy still returns None, damaged still returns the reason string rather than raising
test_doctor_reports_a_healthy_db_normally non-regression: a healthy DB still prints state.db exists (N sessions) with no timeout warning

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass — ran the focused + adjacent files listed above per-file instead; a flat pytest tests/ run is not reproducible locally (three runs, three different failure counts), whereas scripts/run_tests.sh isolates per file
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS 15 (Darwin 25.4), Python 3.11.14

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — docstrings on _db_opens_cleanly, DBHealthProbeTimeout, _ProbeDeadline and _run_abandonable
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A, no config key added (deliberately)
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) — sqlite3.Connection.set_progress_handler and DaemonThreadPoolExecutor are platform-independent; only tested on macOS
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

Contract Protected

Invariant: a health-probe deadline is never a corruption signal and never escalates to repair.

_db_opens_cleanly is the sole input to doctor's "is this database damaged?" decision, and under --fix a non-None return calls repair_state_db_schema(), which backs up and rewrites the file — its last strategy drops the entire messages_fts% schema and VACUUMs. A cancelled probe knows nothing about the database, so it must be unrepresentable as a reason string.

  • Known-bad input: a healthy 5 GB state.db under hermes doctor --fix. PRAGMA integrity_check exceeds the deadline, the progress handler aborts it, SQLite raises OperationalError("interrupted"). Because that is a sqlite3.DatabaseError, the obvious implementation returns "interrupted" as the reason and the user's healthy database is rewritten. Reproduced locally against exactly that shape; repair_state_db_schema fired once on a healthy DB.
  • Structural guarantee: the timeout is raised, not returned, and DBHealthProbeTimeout derives from Exception, not from sqlite3.Error. No present or future except sqlite3.DatabaseError / except sqlite3.OperationalError handler inside the probe can reclassify it as damage. test_progress_handler_cancels_a_real_integrity_check asserts not isinstance(exc, sqlite3.Error) so a later refactor cannot quietly move it under that hierarchy.
  • Future-input coverage: the raise is keyed off _ProbeDeadline.fired — set only when the handler actually returned non-zero — rather than string-matching "interrupted", so a genuinely interrupted statement arising from any other cause is still reported as a real reason and is not swallowed. It is also checked after the probe returns, so it holds no matter which of the probe's internal handlers caught the aborted statement first.
  • Negative case: test_no_deadline_preserves_existing_behaviour pins that with no deadline a damaged file still returns its reason string and never raises, so hermes sessions repair, session_recovery, and the five in-repair verification calls keep the exact contract they have today.
  • Caller-side guarantee: test_probe_timeout_does_not_trigger_repair_under_fix runs run_doctor(fix=True) with a timing-out probe and asserts repair_state_db_schema is called zero times.

Related / Positioning

  • fix(cli): add timeout to state.db health probe in hermes doctor (#72441) #72527 (JonthanaHanh) — same line, same 30 s budget, superseded here: its with ThreadPoolExecutor(...) boundary calls shutdown(wait=True) on exit and so does not bound the command (92.3 s vs 90 s of blocked probe, measured above), it has no test, and it leaves _write_reason = None on timeout, which reports a skipped check as a pass.
  • fix(state): sessions repair should detect incomplete FTS schema, not just unopenable DBs #71933 (Kyzcreig) — also edits _db_opens_cleanly, but a genuinely different defect: it reclassifies the except sqlite3.OperationalError "no such table" branch to distinguish a fresh mid-init file from a store whose FTS trigger outlived its virtual table. This PR deliberately stays out of that block — the deadline is installed in the connection prologue and checked after the probe returns — so the two are independently mergeable and compose correctly (if that new branch is itself interrupted, the post-probe deadline check still converts the result to a timeout rather than a verdict).

…QLite deadline (supersedes NousResearch#72527)

`hermes doctor` stalls indefinitely right after printing
`state.db exists (N sessions)`. The next statement is
`_db_opens_cleanly(state_db_path)`, whose `PRAGMA integrity_check` walks
every page of the file — O(file size), with no output and no way out. The
repo already documents the pathology in its own test suite
(tests/hermes_cli/test_doctor.py, `_isolate_home`: "a multi-minute PRAGMA
integrity_check that blew the 300s per-file budget") and already bounds the
same scan in hermes_cli/backup.py with a byte ceiling.

The probe is now cancellable at the SQLite level. `_db_opens_cleanly` takes
a keyword-only `deadline_seconds` (default `None` = the existing unbounded
contract, so the five `repair_state_db_schema` verification callers,
`hermes sessions repair` and `session_recovery` are untouched). When a
deadline is given it installs a progress handler that aborts the running
statement once the wall clock runs out; measured, `PRAGMA integrity_check`
polls that handler ~170 times on a 41 MB database, so the deadline
genuinely interrupts the scan instead of merely stopping the caller from
waiting on it.

A timeout must never be mistaken for corruption. An aborted statement
raises `sqlite3.OperationalError("interrupted")`, which is a
`sqlite3.DatabaseError` — so returning it as the probe's "reason" string
makes `hermes doctor --fix` call `repair_state_db_schema()`, whose last
strategy drops the whole `messages_fts%` schema and VACUUMs. A
healthy-but-large state.db would be rewritten for being slow. The deadline
is therefore raised as `DBHealthProbeTimeout`, outside the `sqlite3`
exception hierarchy, and doctor reports "health check skipped" and takes
the repair branch only for a real corruption reason.

Around that, doctor runs the probe on `tools.daemon_pool`'s
`DaemonThreadPoolExecutor` with an explicit `shutdown(wait=False)` and a
small grace over the SQLite deadline, for the residual case SQLite cannot
interrupt at all (a `connect()` or page read wedged in an uninterruptible
I/O syscall). `with ThreadPoolExecutor(...)` is deliberately avoided: its
exit calls `shutdown(wait=True)` and blocks until the worker finishes, and
stdlib pool workers are joined unconditionally by
`concurrent.futures.thread._python_exit` even after `shutdown(wait=False)`,
which would only move the hang to interpreter exit.

The `SELECT COUNT(*) FROM sessions` immediately above the probe is inside
the same bounded region. Repair paths are deliberately left unbounded: a
user-requested repair must be allowed to complete.

Fixes NousResearch#72441
Copilot AI review requested due to automatic review settings August 1, 2026 07:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Bounds the hermes doctor state.db health probe so the diagnostic can’t hang indefinitely on large or slow SQLite databases, while preserving the safety invariant that a timeout is not treated as corruption (and therefore never escalates into destructive repair).

Changes:

  • Add a cancellable SQLite-level deadline to _db_opens_cleanly() via a progress handler, surfaced as a new DBHealthProbeTimeout exception (not a sqlite3.Error).
  • Wrap doctor’s state.db reads in an abandonable daemon-thread boundary to ensure the command returns even if SQLite can’t be interrupted (e.g., wedged I/O).
  • Add a focused regression test module covering wall-clock bounding and the “timeout never triggers repair” invariant.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
hermes_state.py Adds progress-handler-based deadlines to the DB health probe and raises a dedicated timeout exception instead of returning a corruption reason.
hermes_cli/doctor.py Applies a bounded/abandonable execution path to the state.db health probe and reports timeouts as “skipped” without entering repair.
tests/hermes_cli/test_doctor_state_db_deadline.py Adds regression tests for bounded execution time and for preventing timeout → repair escalation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread hermes_cli/doctor.py
Comment on lines +1598 to +1600
if count is not None:
check_ok(f"{_DHH}/state.db exists ({count} sessions)")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in d3b6184.

state_db_path.exists() is already true when we get here, so the file's existence is established regardless of whether the probe returned. The boundary-timeout path now emits an explicit row instead of dropping it:

ℹ  ~/.hermes/state.db exists (session count not read — the health check timed out before it returned)

check_info rather than check_ok, because the count genuinely was not read and this should not read as a passed check. test_doctor_returns_without_waiting_for_a_blocked_probe now asserts that line is present, so the row cannot silently disappear again.

…es out

In the worst-case path — the abandonable boundary fires because SQLite
could not be interrupted at all — the session count never comes back and
the `state.db exists (N sessions)` row was dropped entirely, even though
the file's existence is already established. Report existence with an
explicit note that the count was not read.
@briandevans

Copy link
Copy Markdown
Contributor Author

CI audit — the single failure on this branch is a pre-existing baseline on clean origin/main (151e72a5f). Zero failures are in touched code.

Test Symptom Root cause on main
tests/hermes_cli/test_update_eol_churn.py::test_churn_across_more_files_than_fit_in_one_argv AssertionError: assert 45 == 1200 — only 45 of 1200 churned files come back dirty Reproduces identically on a clean 151e72a5f worktree, where the whole file fails 5/9 (test_churn_invisible_under_autocrlf_true_is_still_found, test_churn_is_cleared_and_the_pin_is_persisted, test_real_edits_survive_even_when_line_endings_also_flipped, test_pin_is_withheld_when_the_churn_cannot_be_cleared, and this one). Nothing in this PR touches hermes_cli/update*, git EOL handling, or argv batching.

This PR touches hermes_state.py, hermes_cli/doctor.py, and one new test file. Their suites are green:

file result
tests/hermes_cli/test_doctor_state_db_deadline.py 6 passed
tests/hermes_cli/test_doctor.py 47 passed
tests/test_state_db_malformed_repair.py 9 passed
tests/test_hermes_state.py 140 passed
tests/hermes_cli/test_doctor_command_install.py 3 passed
tests/hermes_cli/test_session_listing.py 6 passed
tests/test_sqlite_wal_reset_gate.py 19 passed

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard area/sessions Session lifecycle, resume, persistence, history sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Aug 1, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related: #72527 addresses the same hermes doctor hang, but its thread-pool timeout still waits during executor shutdown. This PR uses a cancellable SQLite progress-handler deadline and adds timeout-safety coverage; please consolidate on the corrected mechanism.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users area/install-update Installer, updater, packaging, wheels, doctor labels Aug 1, 2026
@stigrunar

Copy link
Copy Markdown

Live reproduction/validation evidence from a large real-world state.db:

  • Environment: Hermes Agent on Linux, Python 3.11.15, SQLite-backed state store.
  • Database logical size: approximately 8.7 GB.
  • Before bounding the probe, hermes doctor exceeded a 420-second outer timeout; a later bounded attempt was killed with exit 137 while the full PRAGMA integrity_check was still the blocking path.
  • A locally tested size-gated containment (skip only the full integrity scan above 1 GiB, retain journal/schema/session/FTS-read/rolled-back-write probes) completed with RC=0 in about 49 seconds on that same store.
  • Focused local validation: 29 state/statistics tests plus 4 doctor-specific tests passed; the live command explicitly reported the deferred integrity scan and continued to render database statistics.

After comparing the open implementations, #76003 looks like the best consolidation lane rather than opening another competing PR. Its cancellable SQLite progress-handler, daemon fallback for uninterruptible I/O, and explicit rule that timeout is inconclusive—not corruption and never a repair trigger—cover the bug class more generally than the local size threshold.

One rebase/non-regression point worth retaining against current main: _db_opens_cleanly now includes the schema/journal check, canonical session read, FTS read probes, and rolled-back FTS write probe. The bounded path should continue exercising those when time permits, and a timeout under --fix must remain non-destructive.

This is supporting live evidence, not a claim that I ran the exact #76003 head against current main.

@alt-glitch alt-glitch removed sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades area/install-update Installer, updater, packaging, wheels, doctor labels Aug 16, 2026
@briandevans

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed 8.7 GB live-database validation, and for calling out the retention points in your comment.

At current head d3b618409c82c81c162686f2522a0c12f50af76e, the bounded path retains the schema/journal validation, canonical session read, FTS read, and rolled-back write probes when time permits. A timeout under --fix also remains non-destructive: it is reported as skipped/inconclusive rather than corruption and does not trigger repair.

This is pinned by test_probe_timeout_does_not_trigger_repair_under_fix, test_probe_timeout_is_reported_as_skipped_not_as_corruption, and test_progress_handler_cancels_a_real_integrity_check.

For clarity, I have not tested the exact live database; the confirmation above is against the current branch implementation and its focused deadline coverage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/sessions Session lifecycle, resume, persistence, history comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: hermes doctor hangs indefinitely after checking state.db (v0.19.0)

5 participants