execute_code approval persistence and guard checks

NousResearch/hermes-agent · cluster 39187 · agent

Data as of

Nothing open on GitHub. Fix merged in #39466. Check it covers your case.

The short answer

0open issues
0open PRs
7items grouped
Known?
Yes. 7 related items grouped here, 2 issues already closed.
Fix event
A fix was merged to main in PR #39466. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this partially resolved, deciding PR #39466. That is separate from the fix event.
Still open
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.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #39466 fix(approval): persist execute_code session/always approvals (#39275)

    merged to main

    files: tests/tools/test_execute_code_approval_cluster.py, tools/approval.py

    Show the diff
    • tests/tools/test_execute_code_approval_cluster.py
      @@ -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
      @@ -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.
      @@ -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}
       

4 PRs closed without merging

From the close comments: done elsewhere 2 · merged via another change 1 · superseded by another PR 1

  • #39204 fix(approval): check permanent allowlist in execute_code guard · done elsewhere → 2574237, #39275
  • #39212 fix(tools): honor execute code permanent approvals · done elsewhere → 2574237, #39466
  • #39391 fix(tools): check is_approved() before prompting in execute_code guard · superseded by another PR → #39466
  • #39413 fix(approval): check is_approved in execute_code guard (#39275) · merged via another change → 2574237, #39466, #39275
Closed issues · 2
  • Closed#39187Bug: execute_code approval "Always" (permanent) doesn't persist — popup reappears on every call
  • Closed#39275execute_code approval: 'Approve session' / 'Always' buttons do not work (missing is_approved check)
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.30: the share of item 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.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
agent
Merged PRs
#39466
Closed issues
2
Attempts
4 PRs closed without merging
Fix link checked
5 Jun 2026

For your agent

The same facts, typed.