{"repo":"NousResearch/hermes-agent","public_id":"54801","display_label":"Dashboard backup CLI argument flag bug","summary":"The dashboard backup feature was passing the output path as a positional argument rather than using the correct -o/--output flag, causing backup failures. Multiple issue reports and fix attempts address this CLI argument syntax mismatch.","subsystem":"dashboard","counts":{"members":7,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":4,"merged_prs":1,"attempt_prs":2},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":64561},"resolution":{"state":"partially_resolved","by":"pr-64561"},"freshness":{"state":"verified","last_verified_at":"2026-07-14T20:55:19+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.24571428571428572},"fix_edges":[{"issue_number":54801,"pr_number":64561,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":54801,"title":"Dashboard backup button passes archive path as positional argument instead of --output flag","url":null,"state":"closed"},{"kind":"issue","number":59078,"title":"Dashboard backup passes output path as bare arg instead of -o","url":null,"state":"closed"},{"kind":"issue","number":62489,"title":"Backup error in macos App","url":null,"state":"closed"},{"kind":"issue","number":65034,"title":"Dashboard Full Backup fails silently in v0.18.2 — CLI argument syntax mismatch","url":null,"state":"closed"},{"kind":"pr","number":55043,"title":"fix(dashboard): pass backup output with -o","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#64561","number":64561}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":62631,"title":"fix(dashboard): pass --output to backup command","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#55043","number":55043},{"kind":"pr","value":"#54801","number":54801},{"kind":"pr","value":"#62489","number":62489}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":64561,"title":"fix(dashboard): pass backup output path with -o instead of positional arg","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_cli/web_server.py","tests/hermes_cli/test_dashboard_admin_endpoints.py","tests/hermes_cli/test_web_server.py"],"fix_hunks":[{"file":"hermes_cli/web_server.py","hunks":["@@ -11915,8 +11915,9 @@ def _new_dashboard_backup_path() -> Path:\n async def run_backup(body: BackupRequest):\n     args = [\"backup\"]\n     archive: Optional[Path] = None\n-    if body.output:\n-        args.append(body.output.strip())\n+    output = (body.output or \"\").strip()\n+    if output:\n+        args.extend([\"-o\", output])\n     else:\n         archive = _new_dashboard_backup_path()\n         try:","@@ -11926,7 +11927,7 @@ async def run_backup(body: BackupRequest):\n                 status_code=500,\n                 detail=f\"Could not create backup directory: {exc}\",\n             )\n-        args.append(str(archive))\n+        args.extend([\"-o\", str(archive)])\n     try:\n         proc = _spawn_hermes_action(args, \"backup\")\n     except Exception as exc:"]},{"file":"tests/hermes_cli/test_dashboard_admin_endpoints.py","hunks":["@@ -338,6 +338,60 @@ class TestOpsEndpoints:\n     def _setup(self, _isolate_hermes_home):\n         self.client, _ = _client()\n \n+    def test_backup_output_uses_output_flag(self, monkeypatch):\n+        import hermes_cli.web_server as ws\n+\n+        captured = {}\n+\n+        class FakeProc:\n+            pid = 12345\n+\n+        def fake_spawn_action(subcommand, name):\n+            captured[\"subcommand\"] = subcommand\n+            captured[\"name\"] = name\n+            return FakeProc()\n+\n+        monkeypatch.setattr(ws, \"_spawn_hermes_action\", fake_spawn_action)\n+\n+        r = self.client.post(\n+            \"/api/ops/backup\",\n+            json={\"output\": \"  /tmp/hermes-test.zip  \"},\n+        )\n+\n+        assert r.status_code == 200\n+        assert captured == {\n+            \"subcommand\": [\"backup\", \"-o\", \"/tmp/hermes-test.zip\"],\n+            \"name\": \"backup\",\n+        }\n+\n+    def test_backup_blank_output_uses_default_archive(self, monkeypatch):\n+        from pathlib import Path\n+\n+        import hermes_cli.web_server as ws\n+        from hermes_cli.config import get_hermes_home\n+\n+        captured = {}\n+\n+        class FakeProc:\n+            pid = 12345\n+\n+        def fake_spawn_action(subcommand, name):\n+            captured[\"subcommand\"] = subcommand\n+            captured[\"name\"] = name\n+            return FakeProc()\n+\n+        monkeypatch.setattr(ws, \"_spawn_hermes_action\", fake_spawn_action)\n+\n+        r = self.client.post(\"/api/ops/backup\", json={\"output\": \"   \"})\n+\n+        assert r.status_code == 200\n+        archive = Path(r.json()[\"archive\"])\n+        assert captured == {\n+            \"subcommand\": [\"backup\", \"-o\", str(archive)],\n+            \"name\": \"backup\",\n+        }\n+        assert archive.parent == get_hermes_home() / \"backups\"\n+\n     def test_hooks_list_reads_config(self):\n         from hermes_cli.config import load_config, save_config\n "]},{"file":"tests/hermes_cli/test_web_server.py","hunks":["@@ -2385,7 +2385,7 @@ def fake_spawn(subcommand, name):\n \n         assert data[\"name\"] == \"backup\"\n         assert captured[\"name\"] == \"backup\"\n-        assert captured[\"args\"] == [\"backup\", str(archive)]\n+        assert captured[\"args\"] == [\"backup\", \"-o\", str(archive)]\n         assert archive.parent == get_hermes_home() / \"backups\"\n         assert archive.name.startswith(\"hermes-backup-\")\n         assert archive.suffix == \".zip\"","@@ -2412,7 +2412,7 @@ def fake_spawn(subcommand, name):\n         archive = Path(resp.json()[\"archive\"])\n \n         assert archive.parent == hosted_home / \"backups\"\n-        assert captured[\"args\"] == [\"backup\", str(archive)]\n+        assert captured[\"args\"] == [\"backup\", \"-o\", str(archive)]\n         assert archive.parent.is_dir()\n \n     def test_ops_backup_download_streams_dashboard_backup(self, tmp_path):\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}