hermes-agent/tests/hermes_cli
Ben Barclay 736e981abf
fix(auth): honor NOUS_INFERENCE_BASE_URL env override for Nous OAuth sessions (#52270)
The host-allowlist hardening (#30611) plus the refresh heal (#49735) left
the documented NOUS_INFERENCE_BASE_URL dev/staging escape hatch unreachable
for OAuth sessions, despite three code comments asserting it still works.

Root cause — resolution precedence in resolve_nous_runtime_credentials:

    inference_base_url = (
        _optional_base_url(state.get("inference_base_url"))  # stored — wins
        or os.getenv("NOUS_INFERENCE_BASE_URL")              # env — unreachable
        or DEFAULT_NOUS_INFERENCE_URL
    )

A staging OAuth login persists its inference_base_url, but the allowlist
rejects the staging host and the refresh heal rewrites the stored value to
the production default. The stored (now prod) value is then read BEFORE the
env var, so the override never takes effect — every request 401s against
prod or is pinned to prod, and setting the env var does nothing.

Fix: the user-set env override is the most-trusted source, so consult it
FIRST for the URL used to build the client / returned to callers — while
keeping the PERSISTED value the validated, network-provenance one (the
override is a runtime overlay, never written to auth.json, so unsetting it
cleanly reverts to prod). Applied at both chokepoints:

- resolve_nous_runtime_credentials (no-refresh read path AND refresh path)
- the nous_portal proxy adapter, which re-validates the resolver's returned
  base_url against the prod allowlist as defense-in-depth and would
  otherwise reject a legitimate staging override at the forward boundary.

New _nous_inference_env_override() / split of stored-vs-effective URL keep
the threat model intact: Portal-returned URLs are still allowlist-validated
at every network site, and the env path stays ungated (trusted OS user).

Also folds in the no-refresh read-path heal (supersedes the approach in
the open #50265): a poisoned stored staging host now heals to the prod
default on read even when no refresh fires.

Tests: TestEnvOverrideWins (env wins on read + refresh paths; override never
persisted; poisoned stored heals) and TestProxyAdapterEnvOverride. Verified
the 4 behavioral tests fail against pre-fix code and pass with the fix; full
inference-validation + nous-provider suites green (85 passed). E2E-validated
against a real temp HERMES_HOME exercising the real resolver + proxy adapter:
resolver→staging, persisted→prod, proxy→staging, unset→reverts to prod.
2026-06-25 00:11:15 -07:00
..
__init__.py
…
conftest.py
…
conftest_dashboard_auth.py
…
test_active_sessions.py fix(tui): preserve live session identity across compression (#49041) 2026-06-24 00:54:18 +05:30
test_anthropic_model_flow_stale_oauth.py
…
test_anthropic_oauth_flow.py
…
test_anthropic_picker_curated.py
…
test_anthropic_provider_persistence.py
…
test_api_key_providers.py
…
test_apply_model_switch_result_context.py
…
test_apply_profile_override.py
…
test_arcee_provider.py
…
test_argparse_flag_propagation.py
…
test_at_context_completion_filter.py
…
test_atomic_json_write.py
…
test_atomic_yaml_write.py fix(config): write config.yaml as UTF-8 to stop emoji/personality corruption (#51676) 2026-06-23 23:28:21 -07:00
test_auth_codex_provider.py
…
test_auth_codex_self_heal.py
…
test_auth_commands.py feat(providers): remove google-gemini-cli + google-antigravity OAuth providers (#50492) 2026-06-21 19:53:27 -07:00
test_auth_loopback_ssh_hint.py
…
test_auth_manual_paste.py
…
test_auth_nous_provider.py fix(auth): honor NOUS_INFERENCE_BASE_URL env override for Nous OAuth sessions (#52270) 2026-06-25 00:11:15 -07:00
test_auth_profile_fallback.py
…
test_auth_provider_gate.py
…
test_auth_qwen_provider.py
…
test_auth_ssl_macos.py
…
test_auth_toctou_file_modes.py
…
test_auth_usable_secret.py
…
test_auth_xai_oauth_provider.py
…
test_aux_config.py
…
test_azure_detect.py
…
test_azure_foundry_entra.py
…
test_backup.py test(backup): add regression tests for restore_quick_snapshot path traversal 2026-06-21 12:44:22 -07:00
test_banner.py fix(banner): don't advertise toolsets/skills the agent wasn't given (#50497) 2026-06-21 19:08:54 -07:00
test_banner_git_state.py
…
test_banner_pip_update.py
…
test_banner_skills.py
…
test_bedrock_model_picker.py
…
test_billing_cli.py
…
test_billing_portal_url.py
…
test_billing_scope_stepup.py
…
test_build_info.py
…
test_bundles.py
…
test_chat_skills_flag.py
…
test_claw.py
…
test_clear_stale_base_url.py
…
test_cli_active_session_limit.py
…
test_cli_output.py
…
test_cmd_update.py fix(update): reuse an existing PATH uv on Termux before pip 2026-06-23 18:42:05 -07:00
test_cmd_update_docker.py
…
test_coalesce_session_args.py
…
test_codex_cli_model_picker.py
…
test_codex_models.py
…
test_codex_runtime_plugin_migration.py
…
test_codex_runtime_switch.py
…
test_commands.py fix(telegram): raise default command-menu cap to 60 so skills stay visible 2026-06-23 23:49:22 -07:00
test_completion.py
…
test_config.py fix(soul): installers seed the real default persona, upgrade legacy empty templates (#52246) 2026-06-24 18:56:26 -07:00
test_config_drift.py
…
test_config_env_expansion.py
…
test_config_env_refs.py
…
test_config_validation.py
…
test_container_aware_cli.py
…
test_container_boot.py fix(gateway): exit 78 (EX_CONFIG) on fatal startup errors, s6 finish script stops restart loop 2026-06-24 16:34:51 +10:00
test_context_switch_guard.py
…
test_copilot_auth.py
…
test_copilot_catalog_oauth_fallback.py
…
test_copilot_context.py
…
test_copilot_in_model_list.py
…
test_copilot_token_exchange.py
…
test_cron.py feat(cron): warn when gateway not running on cron create/list (#51696) 2026-06-23 23:29:50 -07:00
test_cron_fire_dashboard.py fix(cron): complete plugins.cron_providers rename in 2 missed test files 2026-06-23 23:39:22 -07:00
test_cron_parser_builder.py
…
test_ctrlg_editor_submit.py feat(cli): Ctrl+G submits the edited draft on save (TUI parity) (#50560) 2026-06-21 22:43:55 -07:00
test_curator_archive_prune.py
…
test_curator_recent_run_notice.py
…
test_curator_run.py
…
test_curator_status.py
…
test_curses_arrow_keys.py
…
test_curses_color_compat.py
…
test_curses_ui_fuzzy_rank.py
…
test_curses_ui_search.py
…
test_custom_provider_context_length.py
…
test_custom_provider_identity.py
…
test_custom_provider_model_switch.py
…
test_dashboard_admin_endpoints.py
…
test_dashboard_auth_401_reauth.py
…
test_dashboard_auth_audit.py
…
test_dashboard_auth_cookies.py
…
test_dashboard_auth_gate.py fix(security): close hermes-0day MCP-persistence attack surface 2026-06-21 19:05:27 -07:00
test_dashboard_auth_middleware.py
…
test_dashboard_auth_password_login.py
…
test_dashboard_auth_plugin_hook.py
…
test_dashboard_auth_prefix.py
…
test_dashboard_auth_provider_base.py
…
test_dashboard_auth_status_endpoint.py
…
test_dashboard_auth_stub_provider.py
…
test_dashboard_auth_ws_auth.py fix(security): fail-closed when WebSocket peer is empty in loopback mode 2026-06-21 13:33:18 -07:00
test_dashboard_auth_ws_tickets.py
…
test_dashboard_browser_safe_imports.py
…
test_dashboard_lifecycle_flags.py
…
test_dashboard_profiles_nav_label.py
…
test_dashboard_register.py
…
test_dashboard_tui_backcompat.py
…
test_dashboard_unified_launch.py
…
test_debug.py
…
test_default_interface_resolution.py
…
test_dep_ensure.py
…
test_deprecated_cwd_warning.py
…
test_destructive_slash_confirm_gate.py
…
test_detect_api_mode_for_url.py
…
test_determine_api_mode_hostname.py
…
test_dingtalk_auth.py
…
test_discord_skill_clamp_warning.py
…
test_doctor.py feat(providers): remove google-gemini-cli + google-antigravity OAuth providers (#50492) 2026-06-21 19:53:27 -07:00
test_doctor_command_install.py
…
test_doctor_dedicated_provider_skip.py
…
test_dump_git_commit.py
…
test_dump_terminal_backend.py
…
test_ensure_hermes_home_uid_34107.py
…
test_ensure_utf8_locale.py
…
test_env_load_cache.py
…
test_env_loader.py
…
test_env_sanitize_on_load.py
…
test_fallback_cmd.py
…
test_gateway.py fix(gateway): actively reap no-systemd gateway orphan before restart 2026-06-23 23:29:28 -07:00
test_gateway_linger.py
…
test_gateway_platform_gating.py
…
test_gateway_proc_fallback.py fix(gateway): track no-systemd restart runtimes 2026-06-23 23:29:28 -07:00
test_gateway_restart_loop.py
…
test_gateway_runtime_health.py
…
test_gateway_s6_dispatch.py
…
test_gateway_service.py fix(update): announce gateway drain waits so desktop updates don't look hung 2026-06-24 19:12:44 -05:00
test_gateway_service_paths.py
…
test_gateway_windows.py fix(windows): harden gateway scheduled task (#45610) 2026-06-23 16:07:52 -07:00
test_gateway_wsl.py
…
test_gemini_free_tier_setup_block.py
…
test_gemini_provider.py
…
test_gmi_provider.py
…
test_goals.py feat(goals): completion contracts for /goal — evidence-based judging (#50501) 2026-06-22 12:20:09 -07:00
test_graphical_browser_detection.py
…
test_gui_command.py
…
test_gui_uninstall.py
…
test_hooks_cli.py
…
test_ignore_user_config_flags.py
…
test_image_gen_picker.py
…
test_install_cua_driver.py fix(tests): update cua install tests for cross-platform support 2026-06-22 13:41:03 -07:00
test_inventory.py fix(picker): keep flat-namespace reseller first-party models in desktop picker 2026-06-22 06:09:08 -07:00
test_inventory_pricing.py
…
test_kanban_blocked_sticky.py
…
test_kanban_boards.py
…
test_kanban_cli.py
…
test_kanban_cli_dispatch_passthrough.py
…
test_kanban_core_functionality.py refactor(kanban): fold worker/orchestrator skills into injected guidance (#50473) 2026-06-21 17:06:48 -07:00
test_kanban_db.py fix(kanban): bound the cross-process init lock so connect() can't hang forever (#50353) 2026-06-21 12:43:41 -07:00
test_kanban_db_init.py
…
test_kanban_decompose.py
…
test_kanban_decompose_db.py
…
test_kanban_default_assignee.py
…
test_kanban_diagnostics.py
…
test_kanban_dispatch_lock.py fix(kanban): single-writer dispatch lock to prevent orphan-dispatcher DB corruption (#50331) 2026-06-21 12:06:24 -07:00
test_kanban_goal_mode.py feat(goals): /goal wait <pid> — park the loop on a background process (#50503) 2026-06-22 06:27:29 -07:00
test_kanban_init_lock_bounded.py fix(kanban): bound the cross-process init lock so connect() can't hang forever (#50353) 2026-06-21 12:43:41 -07:00
test_kanban_lifecycle_hooks.py feat(kanban): add task lifecycle plugin hooks (claimed/completed/blocked) (#50349) 2026-06-21 12:38:14 -07:00
test_kanban_notify.py
…
test_kanban_per_profile_cap.py
…
test_kanban_promote.py
…
test_kanban_reclaim_claim_lock_guard.py fix(kanban): make reclaim claim-lock-aware to stop task/run status desync (#50366) 2026-06-21 12:49:07 -07:00
test_kanban_specify.py
…
test_kanban_specify_db.py
…
test_kanban_swarm.py
…
test_kanban_worker_image_extraction.py
…
test_kanban_worker_spawn_toolsets.py
…
test_kanban_worker_terminal_cwd.py fix(kanban): pin worker TERMINAL_CWD to the task workspace (#50348) 2026-06-21 12:43:37 -07:00
test_launcher.py
…
test_list_picker_providers.py
…
test_logs.py
…
test_managed_installs.py
…
test_managed_scope.py
…
test_managed_scope_cli_config.py
…
test_managed_scope_config.py
…
test_managed_scope_env.py
…
test_managed_scope_loaders.py
…
test_managed_scope_overlay.py
…
test_managed_scope_regression.py
…
test_managed_scope_surfacing.py
…
test_managed_scope_writeguard.py
…
test_managed_uv.py
…
test_mcp_add_command_dest.py
…
test_mcp_catalog.py
…
test_mcp_config.py
…
test_mcp_reload_confirm_gate.py
…
test_mcp_security.py fix(security): close hermes-0day MCP-persistence attack surface 2026-06-21 19:05:27 -07:00
test_mcp_startup.py
…
test_mcp_tools_config.py
…
test_memory_providers.py
…
test_memory_reset.py
…
test_memory_setup.py
…
test_memory_setup_provider_arg.py
…
test_migrate_xai.py
…
test_model_catalog.py
…
test_model_cost_guard.py
…
test_model_normalize.py
…
test_model_picker_expensive_confirm.py
…
test_model_picker_viewport.py
…
test_model_provider_persistence.py refactor(setup): simplify Z.AI picker — drop dead fallback, fix tests 2026-06-25 12:07:01 +05:30
test_model_switch_configured_provider_routing.py fix(model_switch): route typed configured models off openai-codex (#45006) 2026-06-23 02:03:21 +05:30
test_model_switch_context_display.py
…
test_model_switch_copilot_api_mode.py
…
test_model_switch_custom_providers.py fix(picker): keep flat-namespace reseller first-party models in desktop picker 2026-06-22 06:09:08 -07:00
test_model_switch_opencode_anthropic.py
…
test_model_switch_persist_default.py
…
test_model_switch_variant_tags.py
…
test_model_validation.py
…
test_models.py fix(model-switch): prevent custom-provider misattribution in model picker (#48305) 2026-06-24 19:34:33 +05:30
test_models_dev_preferred_merge.py
…
test_non_ascii_credential.py
…
test_nous_account.py
…
test_nous_auth_keepalive.py fix Nous auth refresh for idle agents 2026-06-21 22:43:48 -07:00
test_nous_auth_status_cache.py
…
test_nous_hermes_non_agentic.py
…
test_nous_inference_url_validation.py fix(auth): honor NOUS_INFERENCE_BASE_URL env override for Nous OAuth sessions (#52270) 2026-06-25 00:11:15 -07:00
test_nous_subscription.py
…
test_ollama_cloud_auth.py
…
test_ollama_cloud_provider.py
…
test_openai_codex_model_validation_fallback.py
…
test_openai_picker_curated.py
…
test_opencode_go_flat_namespace.py
…
test_opencode_go_in_model_list.py
…
test_opencode_go_validation_fallback.py
…
test_overlay_slug_resolution.py
…
test_path_completion.py
…
test_pet_toggle.py
…
test_picker_prewarm.py
…
test_pin_kanban_board_env.py
…
test_pip_install_detection.py
…
test_placeholder_usage.py
…
test_plugin_auxiliary_tasks.py
…
test_plugin_cli_registration.py
…
test_plugin_scanner_recursion.py
…
test_plugins.py docs(plugins): document acting from hooks via ctx.profile_name + dispatch_tool (#50352) 2026-06-21 12:54:40 -07:00
test_plugins_cmd.py
…
test_plugins_cmd_category_discovery.py
…
test_plugins_cmd_enable_disable_nested.py
…
test_plugins_cmd_list.py
…
test_plugins_transcription_registration.py
…
test_plugins_tts_registration.py
…
test_post_setup_gating.py
…
test_profile_describer.py
…
test_profile_distribution.py
…
test_profile_export_credentials.py
…
test_profiles.py fix(gateway): scope dashboard liveness fallback to the profile 2026-06-25 10:25:54 +10:00
test_profiles_s6_hooks.py
…
test_project_plugin_rce_bypass.py revert(plugins): restore user dashboard plugin backend API auto-import (#43719) (#51950) 2026-06-24 07:46:54 -07:00
test_prompt_api_key.py
…
test_prompt_compose_command.py feat(cli): /prompt — compose your next prompt in $EDITOR (#50509) 2026-06-21 20:21:33 -07:00
test_prompt_size.py
…
test_provider_catalog.py feat(providers): remove google-gemini-cli + google-antigravity OAuth providers (#50492) 2026-06-21 19:53:27 -07:00
test_provider_config_validation.py
…
test_provider_groups.py
…
test_provider_live_curated_merge.py
…
test_provider_parity.py
…
test_proxy.py
…
test_psutil_android_extract.py
…
test_pty_bridge.py
…
test_reasoning_effort_menu.py
…
test_reasoning_full_command.py feat(cli): /reasoning full — show complete thinking, not 10-line clamp (#50499) 2026-06-21 20:21:11 -07:00
test_redact_config_bridge.py
…
test_regression_16767.py
…
test_relaunch.py
…
test_resolve_last_session.py
…
test_resolve_provider_openrouter_pool.py
…
test_run_with_idle_timeout.py
…
test_runtime_provider_resolution.py test(delegation): regression for bedrock Claude target_model api_mode routing 2026-06-23 23:49:37 -07:00
test_safe_mode.py
…
test_secret_prompt.py
…
test_secrets_bitwarden_non_tty.py
…
test_security_advisories.py
…
test_security_audit.py
…
test_security_audit_startup.py feat(security): startup security posture audit (warn-on-load) 2026-06-21 19:05:27 -07:00
test_send_cmd.py
…
test_service_manager.py fix(gateway): exit 78 (EX_CONFIG) on fatal startup errors, s6 finish script stops restart loop 2026-06-24 16:34:51 +10:00
test_session_browse.py
…
test_session_handoff.py
…
test_session_recap.py
…
test_sessions_delete.py
…
test_set_config_value.py fix(config): redact api_key in config show/set output (#50245) (#50313) 2026-06-21 11:50:31 -07:00
test_setup.py test(cli): cover non-interactive prompt_yes_no fallback 2026-06-24 17:56:30 +05:30
test_setup_agent_settings.py
…
test_setup_blank_slate.py
…
test_setup_hermes_script.py
…
test_setup_irc.py
…
test_setup_matrix_e2ee.py
…
test_setup_menu_curses_migration.py
…
test_setup_model_provider.py
…
test_setup_noninteractive.py
…
test_setup_ollama_cloud_force_refresh.py
…
test_setup_openclaw_migration.py
…
test_setup_prompt_menus.py
…
test_setup_reconfigure.py
…
test_signal_handler_kanban_worker.py
…
test_skills_config.py
…
test_skills_hub.py
…
test_skills_install_flags.py
…
test_skills_skip_confirm.py
…
test_skills_subparser.py
…
test_skin_engine.py
…
test_slack_cli.py feat(slack): add --no-assistant flag to manifest generation 2026-06-23 11:30:10 -07:00
test_spotify_auth.py
…
test_startup_plugin_gating.py
…
test_status.py
…
test_status_model_provider.py
…
test_subcommands_batch.py
…
test_subcommands_followup.py
…
test_subcommands_profile_gateway.py
…
test_subparser_routing_fallback.py
…
test_subprocess_timeouts.py
…
test_suppress_eio_on_interrupt.py
…
test_system_stats_platform.py
…
test_systemd_optional_directives.py
…
test_teams_pipeline_plugin_cli.py
…
test_telegram_managed_bot.py
…
test_tencent_tokenhub_provider.py
…
test_terminal_menu_fallbacks.py
…
test_timeouts.py
…
test_timestamps_command.py feat(cli): /timestamps command + timestamps in /history (#50506) 2026-06-21 22:44:25 -07:00
test_tips.py
…
test_tool_token_estimation.py
…
test_tools_config.py
…
test_tools_disable_enable.py
…
test_tts_picker.py
…
test_tui_bundled.py
…
test_tui_heap_sizing.py
…
test_tui_mouse_residue_suppression.py
…
test_tui_npm_install.py fix(cli): auto-restore a deleted ui-tui workspace from git before TUI launch 2026-06-21 13:10:22 -07:00
test_tui_resume_flow.py
…
test_uninstall_node_symlinks.py
…
test_update_autostash.py
…
test_update_check.py fix(update): don't count across shallow-clone boundary (bogus '12492 commits behind') (#50784) 2026-06-22 05:39:11 -07:00
test_update_concurrent_quarantine.py fix(gateway): cold-start installed Windows gateway after update when none was running (#50804) 2026-06-22 06:02:31 -07:00
test_update_config_clears_custom_fields.py
…
test_update_hangup_protection.py
…
test_update_interrupted_recovery.py
…
test_update_modified_notice.py
…
test_update_post_pull_syntax_guard.py
…
test_update_stale_dashboard.py
…
test_update_yes_flag.py
…
test_update_zip_atomic_replace.py fix(cli): make ZIP-update directory replace atomic so it can't delete ui-tui 2026-06-21 13:10:22 -07:00
test_update_zip_symlink_reject.py
…
test_user_providers_model_switch.py
…
test_uv_tool_update.py
…
test_verify_core_dependencies.py
…
test_video_gen_picker.py
…
test_voice_wrapper.py
…
test_web_oauth_dispatch.py feat(providers): remove google-gemini-cli + google-antigravity OAuth providers (#50492) 2026-06-21 19:53:27 -07:00
test_web_server.py revert(plugins): restore user dashboard plugin backend API auto-import (#43719) (#51950) 2026-06-24 07:46:54 -07:00
test_web_server_boot_handshake.py test(web_server): add integration tests for desktop boot handshake fix 2026-06-21 12:29:18 -07:00
test_web_server_cron_profiles.py
…
test_web_server_files.py
…
test_web_server_fs.py
…
test_web_server_host_header.py
…
test_web_server_messaging_profiles.py
…
test_web_server_oauth_write.py
…
test_web_server_profile_unification.py
…
test_web_server_pty_import.py
…
test_web_server_session_search.py
…
test_web_server_skill_editor.py
…
test_web_server_skills_profiles.py
…
test_web_ui_build.py
…
test_webhook_cli.py
…
test_whatsapp_cloud_setup.py
…
test_whatsapp_setup_ordering.py
…
test_win_pty_bridge.py
…
test_windows_native_docs.py
…
test_xai_curated_models.py
…
test_xai_model_flow.py
…
test_xai_oauth_pkce_token_exchange.py
…
test_xai_oauth_profile_auth.py
…
test_xai_oauth_refresh.py
…
test_xai_oauth_writethrough.py
…
test_xai_provider_labels.py
…
test_xai_retirement.py
…
test_xiaomi_provider.py
…