# NousResearch/hermes-agent · execute_code approval persistence and guard checks

## Open now

- Nothing open on GitHub.

These issues and fixes address a bug where 'Approve for session' and 'Always' approvals for execute_code were not being persisted or checked correctly, causing the approval prompt to reappear on every call. The fixes involve checking is_approved() and the permanent allowlist in the execute_code guard.

## TL;DR

- 7 member(s): 0 open issue(s), 2 closed issue(s), 0 open PR(s), 1 merged PR(s), 4 PR(s) closed without merging.
- A fix was merged to main in PR #39466. Verify it addresses your case.
- **Recommended:** read PR #39466 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `partially_resolved` · deciding PR(s): `pr-39466`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.30`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #39187: Bug: execute_code approval "Always" (permanent) doesn't persist — popup reappears on every call
- #39275: execute_code approval: 'Approve session' / 'Always' buttons do not work (missing is_approved check)

## Prior & competing PRs

- **#39204: fix(approval): check permanent allowlist in execute_code guard**. closed without merge · fix tier `suggested`
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `25742372eb5664bc9626143a486823b27fb4bc5e`, `#39275`
- **#39466: fix(approval): persist execute_code session/always approvals (#39275)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `tests/tools/test_execute_code_approval_cluster.py`, `tools/approval.py`
  - fix hunks:
  - `tests/tools/test_execute_code_approval_cluster.py`

    ```diff
    @@ -176,6 +176,53 @@ def test_guard_gateway_user_approves_is_one_shot(gw_session):
         assert A.is_approved(gw_session, "execute_code") is False
     
     
    +def test_guard_gateway_user_approves_session_persists(gw_session):
    +    """'Approve session' stores session-level approval (#39275)."""
    +    _register_resolver(gw_session, "session")
    +    res = A.check_execute_code_guard("import os; print(1)", "local")
    +    assert res["approved"] is True
    +    assert res.get("user_approved") is True
    +    # Session approval should now be stored.
    +    assert A.is_approved(gw_session, "execute_code") is True
    +    # Subsequent calls should auto-approve without prompting.
    +    res2 = A.check_execute_code_guard("import os; print(2)", "local")
    +    assert res2["approved"] is True
    +    # Cleanup
    +    with A._lock:
    +        s = A._session_approved.get(gw_session, set())
    +        s.discard("execute_code")
    +
    +
    +def test_guard_gateway_user_approves_always_persists(gw_session):
    +    """'Always' stores permanent approval (#39275)."""
    +    _register_resolver(gw_session, "always")
    +    res = A.check_execute_code_guard("import os; print(1)", "local")
    +    assert res["approved"] is True
    +    assert res.get("user_approved") is True
    +    # Permanent approval should now be stored.
    +    assert A.is_approved(gw_session, "execute_code") is True
    +    # Cleanup
    +    with A._lock:
    +        A._permanent_approved.discard("execute_code")
    +        s = A._session_approved.get(gw_session, set())
    +        s.discard("execute_code")
    +
    +
    +def test_guard_session_approval_short_circuits_prompt(gw_session):
    +    """Once session-approved, execute_code skips the approval prompt (#39275)."""
    +    # Manually set session approval.
    +    A.approve_session(gw_session, "execute_code")
    +    try:
    +        # Even with a denier registered, the is_approved check short-circuits.
    +        _register_resolver(gw_session, "deny")
    +        res = A.check_execute_code_guard("import os", "local")
    +        assert res["approved"] is True
    +    finally:
    +        with A._lock:
    +            s = A._session_approved.get(gw_session, set())
    +            s.discard("execute_code")
    +
    +
     def test_guard_gateway_user_denies_blocks(gw_session):
         _register_resolver(gw_session, "deny")
         res = A.check_execute_code_guard("import os", "local")
    ```
  - `tools/approval.py`

    ```diff
    @@ -1584,6 +1584,12 @@ def check_execute_code_guard(code: str, env_type: str) -> dict:
         # paths don't pay to copy a potentially-large script into this string.
         command = f"execute_code <<'PY'\n{code}\nPY"
     
    +    # Check session/permanent approval — same gate as check_all_command_guards.
    +    # Without this, "Approve session" / "Always" choices are stored but never
    +    # consulted, so every execute_code call re-prompts the user (#39275).
    +    if is_approved(session_key, pattern_key):
    +        return {"approved": True, "message": None}
    +
         # Smart mode: ask the aux LLM about the whole script. An APPROVE here only
         # suppresses the redundant whole-script prompt; the per-call terminal()
         # guards (restored by context propagation) still run independently.
    ```

    ```diff
    @@ -1674,9 +1680,15 @@ def check_execute_code_guard(code: str, env_type: str) -> dict:
                 "user_consent": False,
             }
     
    -    # Approved — one-shot only. Deliberately NO approve_session/approve_permanent:
    -    # each execute_code script is distinct arbitrary code, so approval never
    -    # persists to future scripts.
    +    # Approved — persist based on scope (same logic as check_all_command_guards).
    +    if choice == "session":
    +        approve_session(session_key, pattern_key)
    +    elif choice == "always":
    +        approve_session(session_key, pattern_key)
    +        approve_permanent(pattern_key)
    +        save_permanent_allowlist(_permanent_approved)
    +    # choice == "once": no persistence — approval lasts this single call only.
    +
         return {"approved": True, "message": None,
                 "user_approved": True, "description": description}
     
    ```
- **#39212: fix(tools): honor execute code permanent approvals**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `25742372eb5664bc9626143a486823b27fb4bc5e`, `#39466`, `#39466`
- **#39391: fix(tools): check is_approved() before prompting in execute_code guard**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#39466`
- **#39413: fix(approval): check is_approved in execute_code guard (#39275)**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `25742372e`, `#39466`, `#39275`

## Fix status

- Merged to main in PR #39466 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-06-05`.
- Subsystem: `agent`
