{"repo":"NousResearch/hermes-agent","public_id":"34827","display_label":"Mixed bug fixes across agent and integrations","summary":"These items cover several distinct fixes: concurrent checkpoint preflight side effects in the agent executor, case-insensitive Gmail header handling in Google Workspace, and cleanup behavior for read-only skill-sync backups. No single theme unifies all members.","subsystem":null,"counts":{"members":5,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":2,"attempt_prs":2},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":35254},"resolution":{"state":"resolved","by":"pr-35254"},"freshness":{"state":"verified","last_verified_at":"2026-05-30T09:38:19+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.4},"fix_edges":[{"issue_number":29341,"pr_number":35255,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":34806,"pr_number":35254,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":34827,"pr_number":35255,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false}],"members":[{"kind":"issue","number":34827,"title":"Concurrent checkpoint preflight side effects in tool_executor","url":null,"state":"closed"},{"kind":"pr","number":34856,"title":"fix(run_agent): gate concurrent checkpoint preflight on block_result (fixes #34827)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#35255","number":35255}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":35253,"title":"fix(skills): clean up read-only skill-sync backups; preserve manifest on failure","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"83a7d0b60","number":0},{"kind":"commit","value":"8ae0802d5","number":0}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":35254,"title":"fix(google-workspace): handle Gmail header casing case-insensitively","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["skills/productivity/google-workspace/scripts/google_api.py","tests/skills/test_google_workspace_api.py"]},{"kind":"pr","number":35255,"title":"fix(run_agent): gate concurrent checkpoint preflight on block_result (fixes #34827)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/tool_executor.py","tests/run_agent/test_run_agent.py"],"fix_hunks":[{"file":"agent/tool_executor.py","hunks":["@@ -180,28 +180,9 @@ def execute_tool_calls_concurrent(agent, assistant_message, messages: list, effe\n         except Exception:\n             pass\n \n-        # Checkpoint for file-mutating tools\n-        if function_name in {\"write_file\", \"patch\"} and agent._checkpoint_mgr.enabled:\n-            try:\n-                file_path = function_args.get(\"path\", \"\")\n-                if file_path:\n-                    work_dir = agent._checkpoint_mgr.get_working_dir_for_path(file_path)\n-                    agent._checkpoint_mgr.ensure_checkpoint(work_dir, f\"before {function_name}\")\n-            except Exception:\n-                pass\n-\n-        # Checkpoint before destructive terminal commands\n-        if function_name == \"terminal\" and agent._checkpoint_mgr.enabled:\n-            try:\n-                cmd = function_args.get(\"command\", \"\")\n-                if _is_destructive_command(cmd):\n-                    cwd = function_args.get(\"workdir\") or os.getenv(\"TERMINAL_CWD\", os.getcwd())\n-                    agent._checkpoint_mgr.ensure_checkpoint(\n-                        cwd, f\"before terminal: {cmd[:60]}\"\n-                    )\n-            except Exception:\n-                pass\n-\n+        # ── Block evaluation (BEFORE checkpoint preflight) ───────────\n+        # We must know whether the tool will execute before touching\n+        # checkpoint state (dedup slot, real snapshots).\n         block_result = None\n         blocked_by_guardrail = False\n         if _ts_scope_block is not None:","@@ -224,6 +205,30 @@ def execute_tool_calls_concurrent(agent, assistant_message, messages: list, effe\n                     block_result = agent._guardrail_block_result(guardrail_decision)\n                     blocked_by_guardrail = True\n \n+        # ── Checkpoint preflight (only for tools that will execute) ──\n+        if block_result is None:\n+            # Checkpoint for file-mutating tools\n+            if function_name in {\"write_file\", \"patch\"} and agent._checkpoint_mgr.enabled:\n+                try:\n+                    file_path = function_args.get(\"path\", \"\")\n+                    if file_path:\n+                        work_dir = agent._checkpoint_mgr.get_working_dir_for_path(file_path)\n+                        agent._checkpoint_mgr.ensure_checkpoint(work_dir, f\"before {function_name}\")\n+                except Exception:\n+                    pass\n+\n+            # Checkpoint before destructive terminal commands\n+            if function_name == \"terminal\" and agent._checkpoint_mgr.enabled:\n+                try:\n+                    cmd = function_args.get(\"command\", \"\")\n+                    if _is_destructive_command(cmd):\n+                        cwd = function_args.get(\"workdir\") or os.getenv(\"TERMINAL_CWD\", os.getcwd())\n+                        agent._checkpoint_mgr.ensure_checkpoint(\n+                            cwd, f\"before terminal: {cmd[:60]}\"\n+                        )\n+                except Exception:\n+                    pass\n+\n         parsed_calls.append((tool_call, function_name, function_args, block_result, blocked_by_guardrail))\n \n     # ── Logging / callbacks ──────────────────────────────────────────"]},{"file":"tests/run_agent/test_run_agent.py","hunks":["@@ -2543,6 +2543,122 @@ def test_blocked_memory_tool_does_not_reset_counter(self, agent, monkeypatch):\n         assert json.loads(result) == {\"error\": \"Blocked\"}\n         assert agent._turns_since_memory == 5\n \n+    def test_concurrent_blocked_write_skips_checkpoint(self, agent, monkeypatch):\n+        \"\"\"Concurrent path: blocked write_file should not trigger checkpoint.\"\"\"\n+        tc1 = _mock_tool_call(name=\"write_file\",\n+                              arguments='{\"path\":\"test.txt\",\"content\":\"hello\"}',\n+                              call_id=\"c1\")\n+        tc2 = _mock_tool_call(name=\"read_file\",\n+                              arguments='{\"path\":\"other.py\"}',\n+                              call_id=\"c2\")\n+        mock_msg = _mock_assistant_msg(content=\"\", tool_calls=[tc1, tc2])\n+        messages = []\n+\n+        monkeypatch.setattr(\n+            \"hermes_cli.plugins.get_pre_tool_call_block_message\",\n+            lambda *args, **kwargs: \"Blocked\" if args[0] == \"write_file\" else None,\n+        )\n+\n+        agent._checkpoint_mgr.enabled = True\n+\n+        def fake_handle(name, args, task_id, **kwargs):\n+            return f\"result_{name}\"\n+\n+        with patch(\"run_agent.handle_function_call\", side_effect=fake_handle):\n+            with patch.object(agent._checkpoint_mgr, \"ensure_checkpoint\") as cp_mock:\n+                agent._execute_tool_calls_concurrent(mock_msg, messages, \"task-1\")\n+\n+        cp_mock.assert_not_called()\n+\n+    def test_concurrent_blocked_patch_skips_checkpoint(self, agent, monkeypatch):\n+        \"\"\"Concurrent path: blocked patch should not trigger checkpoint.\"\"\"\n+        tc1 = _mock_tool_call(name=\"patch\",\n+                              arguments='{\"path\":\"f.py\",\"old\":\"a\",\"new\":\"b\"}',\n+                              call_id=\"c1\")\n+        tc2 = _mock_tool_call(name=\"read_file\",\n+                              arguments='{\"path\":\"other.py\"}',\n+                              call_id=\"c2\")\n+        mock_msg = _mock_assistant_msg(content=\"\", tool_calls=[tc1, tc2])\n+        messages = []\n+\n+        monkeypatch.setattr(\n+            \"hermes_cli.plugins.get_pre_tool_call_block_message\",\n+            lambda *args, **kwargs: \"Blocked\" if args[0] == \"patch\" else None,\n+        )\n+\n+        agent._checkpoint_mgr.enabled = True\n+\n+        def fake_handle(name, args, task_id, **kwargs):\n+            return f\"result_{name}\"\n+\n+        with patch(\"run_agent.handle_function_call\", side_effect=fake_handle):\n+            with patch.object(agent._checkpoint_mgr, \"ensure_checkpoint\") as cp_mock:\n+                agent._execute_tool_calls_concurrent(mock_msg, messages, \"task-1\")\n+\n+        cp_mock.assert_not_called()\n+\n+    def test_concurrent_blocked_terminal_skips_checkpoint(self, agent, monkeypatch):\n+        \"\"\"Concurrent path: blocked terminal should not trigger checkpoint.\"\"\"\n+        tc1 = _mock_tool_call(name=\"terminal\",\n+                              arguments='{\"command\":\"rm -rf /tmp/foo\"}',\n+                              call_id=\"c1\")\n+        tc2 = _mock_tool_call(name=\"read_file\",\n+                              arguments='{\"path\":\"other.py\"}',\n+                              call_id=\"c2\")\n+        mock_msg = _mock_assistant_msg(content=\"\", tool_calls=[tc1, tc2])\n+        messages = []\n+\n+        monkeypatch.setattr(\n+            \"hermes_cli.plugins.get_pre_tool_call_block_message\",\n+            lambda *args, **kwargs: \"Blocked\" if args[0] == \"terminal\" else None,\n+        )\n+\n+        agent._checkpoint_mgr.enabled = True\n+\n+        def fake_handle(name, args, task_id, **kwargs):\n+            return f\"result_{name}\"\n+\n+        with patch(\"run_agent.handle_function_call\", side_effect=fake_handle):\n+            with patch.object(agent._checkpoint_mgr, \"ensure_checkpoint\") as cp_mock:\n+                with patch(\"agent.tool_executor._is_destructive_command\", return_value=True):\n+                    agent._execute_tool_calls_concurrent(mock_msg, messages, \"task-1\")\n+\n+        cp_mock.assert_not_called()\n+\n+    def test_concurrent_blocked_write_does_not_steal_slot_from_allowed_write(self, agent, monkeypatch):\n+        \"\"\"When write_file is blocked, its dedup slot must not be consumed,\n+        so a subsequent allowed write_file for the same path still checkpoints.\"\"\"\n+        tc1 = _mock_tool_call(name=\"write_file\",\n+                              arguments='{\"path\":\"dup.txt\",\"content\":\"blocked\"}',\n+                              call_id=\"c1\")\n+        tc2 = _mock_tool_call(name=\"write_file\",\n+                              arguments='{\"path\":\"dup.txt\",\"content\":\"allowed\"}',\n+                              call_id=\"c2\")\n+        mock_msg = _mock_assistant_msg(content=\"\", tool_calls=[tc1, tc2])\n+        messages = []\n+\n+        call_count = {\"n\": 0}\n+        def block_first_only(*args, **kwargs):\n+            call_count[\"n\"] += 1\n+            return \"Blocked\" if call_count[\"n\"] == 1 else None\n+\n+        monkeypatch.setattr(\n+            \"hermes_cli.plugins.get_pre_tool_call_block_message\",\n+            block_first_only,\n+        )\n+\n+        agent._checkpoint_mgr.enabled = True\n+\n+        def fake_handle(name, args, task_id, **kwargs):\n+            return f\"result_{name}\"\n+\n+        with patch(\"run_agent.handle_function_call\", side_effect=fake_handle):\n+            with patch.object(agent._checkpoint_mgr, \"ensure_checkpoint\") as cp_mock:\n+                agent._execute_tool_calls_concurrent(mock_msg, messages, \"task-1\")\n+\n+        # Second (allowed) write must checkpoint even though first was blocked.\n+        cp_mock.assert_called_once()\n+\n \n class TestPathsOverlap:\n     \"\"\"Unit tests for the _paths_overlap helper.\"\"\"\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}