Bug Description
On Windows, a full pre-update backup (hermes update --backup) treats every file whose suffix is .db as SQLite. If HERMES_HOME contains an unrelated non-SQLite .db file—for example, a Windows cache file copied into a Kanban workspace—sqlite3.backup() fails with file is not a database.
_write_full_zip_backup() then aborts the entire archive, deletes the partial ZIP, and returns None. The updater reports that the backup was skipped and continues. As a result, explicitly requesting a full pre-update backup can leave no full pre-update restore artifact.
The latest occurrence was reproduced on Hermes Agent v0.19.1, upstream commit e444d165, on 2026-08-01. The same causal signature was recorded locally on 2026-07-26 and 2026-07-28.
Steps to Reproduce
A minimal deterministic reproduction of the backup helper is:
from pathlib import Path
from tempfile import TemporaryDirectory
from hermes_cli.backup import _write_full_zip_backup
with TemporaryDirectory() as td:
td = Path(td)
home = td / "home"
home.mkdir()
(home / "foreign.db").write_text("not sqlite", encoding="utf-8")
output = td / "pre-update.zip"
result = _write_full_zip_backup(output, home)
print(result)
print(output.exists())
Current result:
The user-facing trigger is:
- Have a non-SQLite file ending in
.db anywhere under a non-excluded path in HERMES_HOME.
- Have an update available.
- Run
hermes update --backup.
- Observe
file is not a database followed by Full-zip backup aborted.
- Observe that the partial pre-update ZIP is deleted and the update continues.
Expected Behavior
The backup implementation should distinguish actual SQLite databases from unrelated files that merely use the .db suffix.
A non-SQLite .db should not invalidate the entire full backup. It should either be copied as a normal file or skipped individually with an explicit warning. Fail-closed behavior should remain for a genuine Hermes SQLite database whose consistent snapshot cannot be created.
If no full backup can be produced, the updater should clearly state that no full rollback archive exists.
Actual Behavior
Every .db suffix is routed through the SQLite backup API. A foreign non-SQLite file therefore causes the full ZIP operation to stop. The partial ZIP is deleted, create_pre_update_backup() returns None, and hermes update continues.
In the latest occurrence, the partial archive had reached approximately 4.5 GB before being deleted. No full pre-update ZIP remained. This is especially risky when the quick snapshot also omits an oversized state.db, because there may then be no current full-state restore artifact.
Affected Component
Setup / Installation
Messaging Platform (if gateway-related)
N/A (CLI only)
Debug Report
Not uploaded because the debug bundle may contain unrelated local profile/session data. This report instead includes a deterministic sanitized reproduction, exact environment/version information, and the relevant sanitized log signature below.
Operating System
Windows 11 Pro 10.0.26200 (build 26200), 64-bit
Python Version
3.11.15
Hermes Version
Hermes Agent v0.19.1 (2026.7.30), upstream e444d165, git installation, up to date
Additional Logs / Traceback (optional)
2026-08-01 00:48:57,987 WARNING hermes_cli.backup: SQLite safe copy failed for <HERMES_HOME>\kanban\boards\<board>\workspaces\<workspace>\product\%SystemDrive%\ProgramData\Microsoft\Windows\Caches\cversions.2.db: file is not a database
2026-08-01 00:48:57,987 WARNING hermes_cli.backup: Full-zip backup aborted: SQLite snapshot failed for kanban\boards\<board>\workspaces\<workspace>\product\%SystemDrive%\ProgramData\Microsoft\Windows\Caches\cversions.2.db
Same causal signature also recorded on:
- 2026-07-26 01:48:41
- 2026-07-26 02:01:38 (a second non-SQLite Windows cache .db filename)
- 2026-07-28 18:57:10
Root Cause Analysis (optional)
At commit e444d165, hermes_cli/backup.py selects the SQLite-safe-copy path solely with:
if abs_path.suffix == ".db":
_safe_copy_db() opens the source through sqlite3 and calls conn.backup(). A non-SQLite file returns False. _write_full_zip_backup() then sets sqlite_snapshot_failed, breaks the archive loop, deletes out_path, and returns None.
_run_pre_update_backup() treats that None as “no files found or write failed” and continues the update, as designed.
I searched existing issues and PRs; none matched this trigger and mechanism. Related but not duplicate:
Proposed Fix (optional)
Route a file through _safe_copy_db() only when it is a known Hermes SQLite database or is positively identified as SQLite. Treat an arbitrary non-SQLite .db as a regular backup file (or skip that file explicitly) without discarding the whole archive.
Please preserve the existing fail-closed invariant for genuine Hermes databases. Suggested regression coverage:
- A foreign non-SQLite
foreign.db does not abort or delete the full archive.
- A genuine SQLite database is still copied through the SQLite backup API.
- A corrupt known Hermes database still fails closed.
- A failed full pre-update backup reports unambiguously that no full rollback archive was created.
Are you willing to submit a PR for this?
Bug Description
On Windows, a full pre-update backup (
hermes update --backup) treats every file whose suffix is.dbas SQLite. IfHERMES_HOMEcontains an unrelated non-SQLite.dbfile—for example, a Windows cache file copied into a Kanban workspace—sqlite3.backup()fails withfile is not a database._write_full_zip_backup()then aborts the entire archive, deletes the partial ZIP, and returnsNone. The updater reports that the backup was skipped and continues. As a result, explicitly requesting a full pre-update backup can leave no full pre-update restore artifact.The latest occurrence was reproduced on Hermes Agent v0.19.1, upstream commit
e444d165, on 2026-08-01. The same causal signature was recorded locally on 2026-07-26 and 2026-07-28.Steps to Reproduce
A minimal deterministic reproduction of the backup helper is:
Current result:
The user-facing trigger is:
.dbanywhere under a non-excluded path inHERMES_HOME.hermes update --backup.file is not a databasefollowed byFull-zip backup aborted.Expected Behavior
The backup implementation should distinguish actual SQLite databases from unrelated files that merely use the
.dbsuffix.A non-SQLite
.dbshould not invalidate the entire full backup. It should either be copied as a normal file or skipped individually with an explicit warning. Fail-closed behavior should remain for a genuine Hermes SQLite database whose consistent snapshot cannot be created.If no full backup can be produced, the updater should clearly state that no full rollback archive exists.
Actual Behavior
Every
.dbsuffix is routed through the SQLite backup API. A foreign non-SQLite file therefore causes the full ZIP operation to stop. The partial ZIP is deleted,create_pre_update_backup()returnsNone, andhermes updatecontinues.In the latest occurrence, the partial archive had reached approximately 4.5 GB before being deleted. No full pre-update ZIP remained. This is especially risky when the quick snapshot also omits an oversized
state.db, because there may then be no current full-state restore artifact.Affected Component
Setup / Installation
Messaging Platform (if gateway-related)
N/A (CLI only)
Debug Report
Not uploaded because the debug bundle may contain unrelated local profile/session data. This report instead includes a deterministic sanitized reproduction, exact environment/version information, and the relevant sanitized log signature below.Operating System
Windows 11 Pro 10.0.26200 (build 26200), 64-bit
Python Version
3.11.15
Hermes Version
Hermes Agent v0.19.1 (2026.7.30), upstream
e444d165, git installation, up to dateAdditional Logs / Traceback (optional)
Root Cause Analysis (optional)
At commit
e444d165,hermes_cli/backup.pyselects the SQLite-safe-copy path solely with:_safe_copy_db()opens the source throughsqlite3and callsconn.backup(). A non-SQLite file returnsFalse._write_full_zip_backup()then setssqlite_snapshot_failed, breaks the archive loop, deletesout_path, and returnsNone._run_pre_update_backup()treats thatNoneas “no files found or write failed” and continues the update, as designed.I searched existing issues and PRs; none matched this trigger and mechanism. Related but not duplicate:
/tmpand incomplete archives when real SQLite databases are large.state.dbcorruption and quick-snapshot capture failures..dbfiles as SQLite.Proposed Fix (optional)
Route a file through
_safe_copy_db()only when it is a known Hermes SQLite database or is positively identified as SQLite. Treat an arbitrary non-SQLite.dbas a regular backup file (or skip that file explicitly) without discarding the whole archive.Please preserve the existing fail-closed invariant for genuine Hermes databases. Suggested regression coverage:
foreign.dbdoes not abort or delete the full archive.Are you willing to submit a PR for this?