NousResearch/hermes-agent

Anthropic fallback incorrectly inheriting non-Anthropic base_url

Cluster 2384 · agent. Data as of

Nothing open on GitHub. Fix merged in #2388 on 21 Mar 2026. Check it covers your case.

The short answer

0open issues
0open PRs
3items grouped
Known?
Yes. 3 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #2388. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this resolved, deciding PR #2388. That is separate from the fix event.
Still open
Nothing open on GitHub.

These issues and fixes address a bug where the Anthropic fallback provider could inherit a base_url from another model (e.g., Codex), causing Claude requests to be sent to the wrong endpoint. A secondary fix for resetting on stash conflict is also bundled in one PR.

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.

  • #2388 fix(provider): prevent Anthropic fallback from inheriting non-Anthropic base_url + fix(update): reset on stash conflict

    merged to main

    files: agent/auxiliary_client.py, hermes_cli/main.py, hermes_cli/runtime_provider.py, tests/hermes_cli/test_update_autostash.py

    Show the diff
    • agent/auxiliary_client.py
      @@ -654,16 +654,20 @@ def _try_anthropic() -> Tuple[Optional[Any], Optional[str]]:
           if not token:
               return None, None
       
      -    # Allow base URL override from config.yaml model.base_url
      +    # Allow base URL override from config.yaml model.base_url, but only
      +    # when the configured provider is anthropic — otherwise a non-Anthropic
      +    # base_url (e.g. Codex endpoint) would leak into Anthropic requests.
           base_url = _ANTHROPIC_DEFAULT_BASE_URL
           try:
               from hermes_cli.config import load_config
               cfg = load_config()
               model_cfg = cfg.get("model")
               if isinstance(model_cfg, dict):
      -            cfg_base_url = (model_cfg.get("base_url") or "").strip().rstrip("/")
      -            if cfg_base_url:
      -                base_url = cfg_base_url
      +            cfg_provider = str(model_cfg.get("provider") or "").strip().lower()
      +            if cfg_provider == "anthropic":
      +                cfg_base_url = (model_cfg.get("base_url") or "").strip().rstrip("/")
      +                if cfg_base_url:
      +                    base_url = cfg_base_url
           except Exception:
               pass
       
    • hermes_cli/main.py
      @@ -2559,12 +2559,29 @@ def _restore_stashed_changes(
               capture_output=True,
               text=True,
           )
      -    if restore.returncode != 0:
      +
      +    # Check for unmerged (conflicted) files — can happen even when returncode is 0
      +    unmerged = subprocess.run(
      +        git_cmd + ["diff", "--name-only", "--diff-filter=U"],
      +        cwd=cwd,
      +        capture_output=True,
      +        text=True,
      +    )
      +    has_conflicts = bool(unmerged.stdout.strip())
      +
      +    if restore.returncode != 0 or has_conflicts:
      +        # Reset the working tree so Hermes is runnable with the updated code
      +        subprocess.run(
      +            git_cmd + ["reset", "--hard", "HEAD"],
      +            cwd=cwd,
      +            capture_output=True,
      +        )
               print("✗ Update pulled new code, but restoring local changes failed.")
               if restore.stdout.strip():
                   print(restore.stdout.strip())
               if restore.stderr.strip():
                   print(restore.stderr.strip())
      +        print("The working tree has been reset to a clean state.")
               print("Your changes are still preserved in git stash.")
               print(f"Resolve manually with: git stash apply {stash_ref}")
               sys.exit(1)
    • hermes_cli/runtime_provider.py
      @@ -359,9 +359,14 @@ def resolve_runtime_provider(
                       "No Anthropic credentials found. Set ANTHROPIC_TOKEN or ANTHROPIC_API_KEY, "
                       "run 'claude setup-token', or authenticate with 'claude /login'."
                   )
      -        # Allow base URL override from config.yaml model.base_url
      +        # Allow base URL override from config.yaml model.base_url, but only
      +        # when the configured provider is anthropic — otherwise a non-Anthropic
      +        # base_url (e.g. Codex endpoint) would leak into Anthropic requests.
               model_cfg = _get_model_config()
      -        cfg_base_url = (model_cfg.get("base_url") or "").strip().rstrip("/")
      +        cfg_provider = str(model_cfg.get("provider") or "").strip().lower()
      +        cfg_base_url = ""
      +        if cfg_provider == "anthropic":
      +            cfg_base_url = (model_cfg.get("base_url") or "").strip().rstrip("/")
               base_url = cfg_base_url or "https://api.anthropic.com"
               return {
                   "provider": "anthropic",
    • tests/hermes_cli/test_update_autostash.py
      @@ -68,6 +68,8 @@ def fake_run(cmd, **kwargs):
               calls.append((cmd, kwargs))
               if cmd[1:3] == ["stash", "apply"]:
                   return SimpleNamespace(stdout="applied\n", stderr="", returncode=0)
      +        if cmd[1:3] == ["diff", "--name-only"]:
      +            return SimpleNamespace(stdout="", stderr="", returncode=0)
               if cmd[1:3] == ["stash", "list"]:
                   return SimpleNamespace(stdout="stash@{1} abc123\n", stderr="", returncode=0)
               if cmd[1:3] == ["stash", "drop"]:
      @@ -81,8 +83,9 @@ def fake_run(cmd, **kwargs):
       
           assert restored is True
           assert calls[0][0] == ["git", "stash", "apply", "abc123"]
      -    assert calls[1][0] == ["git", "stash", "list", "--format=%gd %H"]
      -    assert calls[2][0] == ["git", "stash", "drop", "stash@{1}"]
      +    assert calls[1][0] == ["git", "diff", "--name-only", "--diff-filter=U"]
      +    assert calls[2][0] == ["git", "stash", "list", "--format=%gd %H"]
      +    assert calls[3][0] == ["git", "stash", "drop", "stash@{1}"]
           out = capsys.readouterr().out
           assert "Restore local changes now? [Y/n]" in out
           assert "restored on top of the updated codebase" in out
      @@ -117,6 +120,8 @@ def fake_run(cmd, **kwargs):
               calls.append((cmd, kwargs))
               if cmd[1:3] == ["stash", "apply"]:
                   return SimpleNamespace(stdout="applied\n", stderr="", returncode=0)
      +        if cmd[1:3] == ["diff", "--name-only"]:
      +            return SimpleNamespace(stdout="", stderr="", returncode=0)
               if cmd[1:3] == ["stash", "list"]:
                   return SimpleNamespace(stdout="stash@{0} abc123\n", stderr="", returncode=0)
               if cmd[1:3] == ["stash", "drop"]:
      @@ -129,8 +134,9 @@ def fake_run(cmd, **kwargs):
       
           assert restored is True
           assert calls[0][0] == ["git", "stash", "apply", "abc123"]
      -    assert calls[1][0] == ["git", "stash", "list", "--format=%gd %H"]
      -    assert calls[2][0] == ["git", "stash", "drop", "stash@{0}"]
      +    assert calls[1][0] == ["git", "diff", "--name-only", "--diff-filter=U"]
      +    assert calls[2][0] == ["git", "stash", "list", "--format=%gd %H"]
      +    assert calls[3][0] == ["git", "stash", "drop", "stash@{0}"]
           assert "Restore local changes now?" not in capsys.readouterr().out
       
       
      @@ -152,6 +158,8 @@ def fake_run(cmd, **kwargs):
               calls.append((cmd, kwargs))
               if cmd[1:3] == ["stash", "apply"]:
                   return SimpleNamespace(stdout="applied\n", stderr="", returncode=0)
      +        if cmd[1:3] == ["diff", "--name-only"]:
      +            return SimpleNamespace(stdout="", stderr="", returncode=0)
               if cmd[1:3] == ["stash", "list"]:
                   return SimpleNamespace(stdout="stash@{0} def456\n", stderr="", returncode=0)
               raise AssertionError(f"unexpected command: {cmd}")
      @@ -161,10 +169,9 @@ def fake_run(cmd, **kwargs):
           restored = hermes_main._restore_stashed_changes(["git"], tmp_path, "abc123", prompt_user=False)
       
           assert restored is True
      -    assert calls == [
      -        (["git", "stash", "apply", "abc123"], {"cwd": tmp_path, "capture_output": True, "text": True}),
      -        (["git", "stash", "list", "--format=%gd %H"], {"cwd": tmp_path, "capture_output": True, "text": True, "check": True}),
      -    ]
      +    assert calls[0] == (["git", "stash", "apply", "abc123"], {"cwd": tmp_path, "capture_output": True, "text": True})
      +    assert calls[1] == (["git", "diff", "--name-only", "--diff-filter=U"], {"cwd": tmp_path, "capture_output": True, "text": True})
      +    assert calls[2] == (["git", "stash", "list", "--format=%gd %H"], {"cwd": tmp_path, "capture_output": True, "text": True, "check": True})
           out = capsys.readouterr().out
           assert "couldn't find the stash entry to drop" in out
           assert "stash was left in place" in out
      @@ -181,6 +188,8 @@ def fake_run(cmd, **kwargs):
               calls.append((cmd, kwargs))
               if cmd[1:3] == ["stash", "apply"]:
                   return SimpleNamespace(stdout="applied\n", stderr="", returncode=0)
      +        if cmd[1:3] == ["diff", "--name-only"]:
      +            return SimpleNamespace(stdout="", stderr="", returncode=0)
               if cmd[1:3] == ["stash", "list"]:
                   return SimpleNamespace(stdout="stash@{0} abc123\n", stderr="", returncode=0)
               if cmd[1:3] == ["stash", "drop"]:
      @@ -192,7 +201,7 @@ def fake_run(cmd, **kwargs):
           restored = hermes_main._restore_stashed_changes(["git"], tmp_path, "abc123", prompt_user=False)
       
           assert restored is True
      -    assert calls[2][0] == ["git", "stash", "drop", "stash@{0}"]
      +    assert calls[3][0] == ["git", "stash", "drop", "stash@{0}"]
           out = capsys.readouterr().out
           assert "couldn't drop the saved stash entry" in out
           assert "drop failed" in out
      @@ -208,6 +217,10 @@ def fake_run(cmd, **kwargs):
               calls.append((cmd, kwargs))
               if cmd[1:3] == ["stash", "apply"]:
                   return SimpleNamespace(stdout="conflict output\n", stderr="conflict stderr\n", returncode=1)
      +        if cmd[1:3] == ["diff", "--name-only"]:
      +            return SimpleNamespace(stdout="hermes_cli/main.py\n", stderr="", returncode=0)
      +        if cmd[1:3] == ["reset", "--hard"]:
      +            return SimpleNamespace(stdout="", stderr="", returncode=0)
               raise AssertionError(f"unexpected command: {cmd}")
       
           monkeypatch.setattr(hermes_main.subprocess, "run", fake_run)
      @@ -219,7 +232,34 @@ def fake_run(cmd, **kwargs):
           out = capsys.readouterr().out
           assert "Your changes are still preserved in git stash." in out
           assert "git stash apply abc123" in out
      -    assert calls == [(["git", "stash", "apply", "abc123"], {"cwd": tmp_path, "capture_output": True, "text": True})]
      +    assert "working tree has been reset to a clean state" in out
      +    # Verify reset --hard was called to clean up conflict markers
      +    reset_calls = [c for c, _ in calls if c[1:3] == ["reset", "--hard"]]
      +    assert len(reset_calls) == 1
      +
      +
      +def test_restore_stashed_changes_resets_when_unmerged_files_detected(monkeypatch, tmp_path, capsys):
      +    """Even if stash apply returns 0, conflict markers must be cleaned up."""
      +    calls = []
      +
      +    def fake_run(cmd, **kwargs):
      +        calls.append((cmd, kwargs))
      +        if cmd[1:3] == ["stash", "apply"]:
      +            return SimpleNamespace(stdout="applied\n", stderr="", returncode=0)
      +        if cmd[1:3] == ["diff", "--name-only"]:
      +            return SimpleNamespace(stdout="cli.py\n", stderr="", returncode=0)
      +        if cmd[1:3] == ["reset", "--hard"]:
      +            return SimpleNamespace(stdout="", stderr="", returncode=0)
      +        raise AssertionError(f"unexpected command: {cmd}")
      +
      +    monkeypatch.setattr(hermes_main.subprocess, "run", fake_run)
      +
      +    with pytest.raises(SystemExit, match="1"):
      +        hermes_main._restore_stashed_changes(["git"], tmp_path, "abc123", prompt_user=False)
      +
      +    out = capsys.readouterr().out
      +    assert "working tree has been reset to a clean state" in out
      +    assert "git stash apply abc123" in out
       
       
       def test_stash_local_changes_if_needed_raises_when_stash_ref_missing(monkeypatch, tmp_path):

1 PR closed without merging

From the close comments: merged via another change 1

  • #2385 fix: Anthropic fallback can inherit Codex model.base_url and send Clau · merged via another change → #2388
Closed issues · 1
  • Closed#2384Anthropic fallback can inherit Codex model.base_url and send Claude requests to chatgpt.com/backend-api/codex
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.67: 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
#2388
Closed issues
1
Attempts
1 PR closed without merging
Fix link checked
21 Mar 2026. This confirmation is old and may be out of date (a revert or reopen would not have invalidated it here); re-verify.

For your agent

The same facts, typed.