hermes - ✅(Solved) Fix Bug: stale reasoning reused when current turn has no reasoning_content [3 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#17052Fetched 2026-04-29 06:37:34
View on GitHub
Comments
0
Participants
1
Timeline
6
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×3labeled ×3

Root Cause

# Current code (run_agent.py ~L13108)
last_reasoning = None
for msg in reversed(messages):
    if msg.get("role") == "assistant" and msg.get("reasoning"):  # ← skips current msg when reasoning=None
        last_reasoning = msg["reasoning"]                        # ← matches older msg instead
        break

The and msg.get("reasoning") condition causes the loop to skip the last assistant message when its reasoning is None/falsy, continuing backwards until it finds any older message that has reasoning.

Fix Action

Fix

Stop at the last assistant message regardless of whether its reasoning field is populated:

last_reasoning = None
for msg in reversed(messages):
    if msg.get("role") == "assistant":
        last_reasoning = msg.get("reasoning")  # None is the correct result when no reasoning
        break

PR fix notes

PR #17055: fix: prevent stale reasoning from being reused across turns

Description (problem / solution / changelog)

Summary

Fixes #17052

The last_reasoning extraction loop walked backwards through the entire message history looking for any assistant message with a truthy reasoning field. When the current turn produced no reasoning (e.g. the model returned reasoning_content=null for a simple prompt), the loop would skip the current message and match an older assistant message, displaying its reasoning as if it belonged to the current response.

Changes

run_agent.pyrun_conversation() last_reasoning extraction:

# Before (walks backwards until finding any reasoning)
for msg in reversed(messages):
    if msg.get("role") == "assistant" and msg.get("reasoning"):
        last_reasoning = msg["reasoning"]
        break

# After (stops at the last assistant message)
for msg in reversed(messages):
    if msg.get("role") == "assistant":
        last_reasoning = msg.get("reasoning")
        break

Testing

  • Confirmed on a live deployment using GLM-5 (which returns reasoning_content: null for simple prompts and actual reasoning for complex ones)
  • Before fix: simple "hello" message displayed reasoning from a previous complex turn
  • After fix: simple messages show no reasoning; complex messages show their own reasoning correctly

Impact

  • Affects models that occasionally return reasoning_content: null (GLM-5, some DeepSeek/Qwen configurations)
  • No behavior change for models that always return reasoning or never return reasoning
  • Minimal change: 2 lines modified, no new dependencies

Changed files

  • run_agent.py (modified, +7/-3)

PR #1: fix: resolve 7 identified issues [automated]

Description (problem / solution / changelog)

Resumo

Este PR corrige 7 issues identificados no repositório NousResearch/hermes-agent.


Issues Corrigidos

1. #17086 - custom endpoint com api_mode=anthropic_messages falhava com 404

Arquivo: agent/auxiliary_client.py Problema: Quando provider=custom com api_mode=anthropic_messages e base_url terminando em /anthropic, a funcao _resolve_provider_client() convertia a URL para o formato /v1/messages, causando 404 em provedores Anthropic-compatíveis de terceiros. Correcao: Mantem o path /anthropic quando api_mode=anthropic_messages e o base_url ja termina em /anthropic.


2. #17076 - kimi-coding vision quebrado (404 em analise de imagem)

Arquivo: agent/auxiliary_client.py Problema: kimi-coding nao estava listado em _PROVIDER_VISION_MODELS, entao auxiliary.vision.provider: auto nao conseguia detectar o modelo de visao disponivel para Kimi. Correcao: Adicionado kimi-coding e kimi-coding-cn ao mapa de modelos de visao.


3. #17080 - hermes profile create --clone copiava credenciais exclusivas de plataforma

Arquivo: hermes_cli/profiles.py Problema: O clone de perfil copiava TELEGRAM_BOT_TOKEN, DISCORD_BOT_TOKEN, WEIXIN_TOKEN verbatim. Quando dois perfis iniciam simultaneamente com o mesmo token, o adaptador de plataforma falha durante a aquisicao de lock. Correcao: Define _EXCLUSIVE_PLATFORM_KEYS e _EXCLUSIVE_PLATFORM_CONFIG_PATHS. Credenciais exclusivas sao comentadas em .env e entradas de plataforma sao enabled: false em config.yaml apos clone.


4. #17054 - Slack manifest rejeitava nomes com underscore

Arquivo: hermes_cli/commands.py Problema: _sanitize_slack_name() convertia nomes de comandos como _reload_mcp para Slack mas nao removia o prefixo underscore, causando rejeicao do manifest. Correcao: Adicionada verificacao para pular nomes que começam com underscore antes de adicionar a lista de slash commands.


5. #17057 - custom Kimi-compatible endpoint falhava apos tool call com thinking habilitado

Arquivo: run_agent.py Problema: _needs_kimi_tool_reasoning() só verificava hostnames oficiais (api.kimi.com, moonshot.ai, moonshot.cn). Endpoints Kimi-compatíveis customizados nao eram detectados. Correcao: Ampliada a verificacao para detectar endpoints customizados pela familia do modelo (nome contem "kimi" ou "k2") alem do hostname.


6. #17049 - UnicodeDecodeError no scan de processos Windows (wmic)

Arquivo: hermes_cli/gateway.py Problema: wmic emitia saida em encoding local do Windows (cp1252/utf-16), causando UnicodeDecodeError e AttributeError durante parsing. Correcao: O parsing agora usa errors=ignore no decode, tratando bytes invalidos como despreziveis.


7. #17052 - stale reasoning reutilizado quando turn atual nao tem reasoning_content

Arquivo: run_agent.py Problema: Mensagens de assistant com tool_calls e reasoning_content eram reutilizadas indevidamente em turns que nao tinham reasoning_content, causando confabulacoes em provedores como Qwen3.6:27b via Ollama. Correcao: O loop de replay agora detecta quando a mensagem atual e um assistant com tool_calls mas sem reasoning_content, e limpa msg[reasoning_content] e msg[reasoning] para evitar propagacao de estado de reasoning de turns anteriores.


Arquivos Modificados

  • agent/auxiliary_client.py - #17086, #17076
  • hermes_cli/profiles.py - #17080
  • hermes_cli/commands.py - #17054
  • run_agent.py - #17057, #17052
  • hermes_cli/gateway.py - #17049

Notas

  • Este PR contem 8 commits (7 issues + 1 fix de seguranca do upstream relacionado a redaction de secrets)
  • Todos os commits foram feitos na branch fix-7-issues-clean
  • Nenhum push intermediario foi feito - push unico ao final
  • Commits do upstream incluidos para manter o historico completo: #16843, #17041, #17039

Changed files

  • .gitignore (modified, +1/-0)
  • Dockerfile (modified, +6/-2)
  • acp_adapter/entry.py (modified, +11/-0)
  • acp_adapter/server.py (modified, +28/-1)
  • agent/anthropic_adapter.py (modified, +134/-74)
  • agent/auxiliary_client.py (modified, +325/-53)
  • agent/bedrock_adapter.py (modified, +41/-3)
  • agent/context_compressor.py (modified, +113/-5)
  • agent/credential_pool.py (modified, +82/-4)
  • agent/credential_sources.py (modified, +0/-1)
  • agent/error_classifier.py (modified, +32/-0)
  • agent/gemini_cloudcode_adapter.py (modified, +0/-2)
  • agent/gemini_schema.py (modified, +1/-1)
  • agent/google_code_assist.py (modified, +0/-1)
  • agent/google_oauth.py (modified, +3/-3)
  • agent/image_routing.py (added, +236/-0)
  • agent/memory_manager.py (modified, +113/-5)
  • agent/model_metadata.py (modified, +56/-21)
  • agent/nous_rate_guard.py (modified, +144/-1)
  • agent/onboarding.py (added, +191/-0)
  • agent/prompt_builder.py (modified, +38/-0)
  • agent/redact.py (modified, +13/-6)
  • agent/shell_hooks.py (modified, +7/-2)
  • agent/skill_commands.py (modified, +2/-2)
  • agent/title_generator.py (modified, +39/-5)
  • agent/transports/anthropic.py (modified, +1/-7)
  • agent/transports/chat_completions.py (modified, +74/-0)
  • agent/transports/codex.py (modified, +1/-3)
  • cli-config.yaml.example (modified, +28/-8)
  • cli.py (modified, +522/-195)
  • cron/jobs.py (modified, +34/-5)
  • cron/scheduler.py (modified, +39/-5)
  • docker/entrypoint.sh (modified, +9/-7)
  • flake.nix (modified, +1/-0)
  • gateway/channel_directory.py (modified, +67/-14)
  • gateway/config.py (modified, +84/-3)
  • gateway/display_config.py (modified, +3/-1)
  • gateway/mirror.py (modified, +57/-11)
  • gateway/pairing.py (modified, +2/-1)
  • gateway/platforms/__init__.py (modified, +2/-0)
  • gateway/platforms/base.py (modified, +233/-16)
  • gateway/platforms/discord.py (modified, +18/-24)
  • gateway/platforms/email.py (modified, +3/-0)
  • gateway/platforms/feishu_comment.py (modified, +0/-1)
  • gateway/platforms/helpers.py (modified, +11/-2)
  • gateway/platforms/matrix.py (modified, +493/-47)
  • gateway/platforms/mattermost.py (modified, +0/-1)
  • gateway/platforms/qqbot/adapter.py (modified, +2/-7)
  • gateway/platforms/slack.py (modified, +753/-70)
  • gateway/platforms/telegram.py (modified, +138/-14)
  • gateway/platforms/weixin.py (modified, +26/-3)
  • gateway/platforms/yuanbao.py (added, +4754/-0)
  • gateway/platforms/yuanbao_media.py (added, +645/-0)
  • gateway/platforms/yuanbao_proto.py (added, +1209/-0)
  • gateway/platforms/yuanbao_sticker.py (added, +558/-0)
  • gateway/run.py (modified, +1143/-283)
  • gateway/runtime_footer.py (added, +150/-0)
  • gateway/session.py (modified, +16/-21)
  • gateway/stream_consumer.py (modified, +110/-0)
  • gateway/whatsapp_identity.py (modified, +21/-1)
  • hermes_cli/auth.py (modified, +40/-4)
  • hermes_cli/azure_detect.py (modified, +1/-1)
  • hermes_cli/backup.py (modified, +272/-1)
  • hermes_cli/banner.py (modified, +0/-1)
  • hermes_cli/claw.py (modified, +67/-6)
  • hermes_cli/commands.py (modified, +119/-5)
  • hermes_cli/config.py (modified, +322/-29)
  • hermes_cli/debug.py (modified, +13/-7)
  • hermes_cli/dingtalk_auth.py (modified, +0/-1)
  • hermes_cli/doctor.py (modified, +11/-1)
  • hermes_cli/env_loader.py (modified, +2/-1)
  • hermes_cli/fallback_cmd.py (added, +361/-0)
  • hermes_cli/gateway.py (modified, +47/-12)
  • hermes_cli/hooks.py (modified, +1/-2)
  • hermes_cli/main.py (modified, +691/-58)
  • hermes_cli/model_catalog.py (added, +329/-0)
  • hermes_cli/model_switch.py (modified, +55/-6)
  • hermes_cli/models.py (modified, +251/-43)
  • hermes_cli/nous_subscription.py (modified, +16/-8)
  • hermes_cli/oneshot.py (modified, +28/-11)
  • hermes_cli/platforms.py (modified, +1/-0)
  • hermes_cli/plugins.py (modified, +14/-0)
  • hermes_cli/plugins_cmd.py (modified, +0/-1)
  • hermes_cli/profiles.py (modified, +199/-4)
  • hermes_cli/providers.py (modified, +26/-0)
  • hermes_cli/runtime_provider.py (modified, +100/-14)
  • hermes_cli/setup.py (modified, +69/-16)
  • hermes_cli/skills_hub.py (modified, +230/-20)
  • hermes_cli/slack_cli.py (added, +152/-0)
  • hermes_cli/status.py (modified, +3/-2)
  • hermes_cli/timeouts.py (modified, +4/-4)
  • hermes_cli/tips.py (modified, +2/-3)
  • hermes_cli/tools_config.py (modified, +173/-4)
  • hermes_cli/web_server.py (modified, +11/-14)
  • hermes_cli/webhook.py (modified, +2/-2)
  • hermes_logging.py (modified, +3/-4)
  • hermes_state.py (modified, +578/-164)
  • model_tools.py (modified, +45/-10)
  • nix/checks.nix (modified, +30/-3)
  • nix/hermes-agent.nix (added, +186/-0)

PR #17090: fix: resolve 7 identified issues [automated]

Description (problem / solution / changelog)

Resumo

Este PR corrige 7 issues identificados no repositório NousResearch/hermes-agent.


Issues Corrigidos

1. #17086 - custom endpoint com api_mode=anthropic_messages falhava com 404

Arquivo: agent/auxiliary_client.py Problema: Quando provider=custom com api_mode=anthropic_messages e base_url terminando em /anthropic, a funcao _resolve_provider_client() convertia a URL para o formato /v1/messages, causando 404 em provedores Anthropic-compatíveis de terceiros. Correcao: Mantem o path /anthropic quando api_mode=anthropic_messages e o base_url ja termina em /anthropic.


2. #17076 - kimi-coding vision quebrado (404 em analise de imagem)

Arquivo: agent/auxiliary_client.py Problema: kimi-coding nao estava listado em _PROVIDER_VISION_MODELS, entao auxiliary.vision.provider: auto nao conseguia detectar o modelo de visao disponivel para Kimi. Correcao: Adicionado kimi-coding e kimi-coding-cn ao mapa de modelos de visao.


3. #17080 - hermes profile create --clone copiava credenciais exclusivas de plataforma

Arquivo: hermes_cli/profiles.py Problema: O clone de perfil copiava TELEGRAM_BOT_TOKEN, DISCORD_BOT_TOKEN, WEIXIN_TOKEN verbatim. Quando dois perfis iniciam simultaneamente com o mesmo token, o adaptador de plataforma falha durante a aquisicao de lock. Correcao: Define _EXCLUSIVE_PLATFORM_KEYS e _EXCLUSIVE_PLATFORM_CONFIG_PATHS. Credenciais exclusivas sao comentadas em .env e entradas de plataforma sao enabled: false em config.yaml apos clone.


4. #17054 - Slack manifest rejeitava nomes com underscore

Arquivo: hermes_cli/commands.py Problema: _sanitize_slack_name() convertia nomes de comandos como _reload_mcp para Slack mas nao removia o prefixo underscore, causando rejeicao do manifest. Correcao: Adicionada verificacao para pular nomes que começam com underscore antes de adicionar a lista de slash commands.


5. #17057 - custom Kimi-compatible endpoint falhava apos tool call com thinking habilitado

Arquivo: run_agent.py Problema: _needs_kimi_tool_reasoning() só verificava hostnames oficiais (api.kimi.com, moonshot.ai, moonshot.cn). Endpoints Kimi-compatíveis customizados nao eram detectados. Correcao: Ampliada a verificacao para detectar endpoints customizados pela familia do modelo (nome contem "kimi" ou "k2") alem do hostname.


6. #17049 - UnicodeDecodeError no scan de processos Windows (wmic)

Arquivo: hermes_cli/gateway.py Problema: wmic emitia saida em encoding local do Windows (cp1252/utf-16), causando UnicodeDecodeError e AttributeError durante parsing. Correcao: O parsing agora usa errors=ignore no decode, tratando bytes invalidos como despreziveis.


7. #17052 - stale reasoning reutilizado quando turn atual nao tem reasoning_content

Arquivo: run_agent.py Problema: Mensagens de assistant com tool_calls e reasoning_content eram reutilizadas indevidamente em turns que nao tinham reasoning_content, causando confabulacoes em provedores como Qwen3.6:27b via Ollama. Correcao: O loop de replay agora detecta quando a mensagem atual e um assistant com tool_calls mas sem reasoning_content, e limpa msg[reasoning_content] e msg[reasoning] para evitar propagacao de estado de reasoning de turns anteriores.


Arquivos Modificados

  • agent/auxiliary_client.py - #17086, #17076
  • hermes_cli/profiles.py - #17080
  • hermes_cli/commands.py - #17054
  • run_agent.py - #17057, #17052
  • hermes_cli/gateway.py - #17049

Notas

  • Este PR contem 8 commits (7 issues + 1 fix de seguranca do upstream relacionado a redaction de secrets)
  • Todos os commits foram feitos na branch fix-7-issues-clean
  • Nenhum push intermediario foi feito - push unico ao final
  • Commits do upstream incluidos para manter o historico completo: #16843, #17041, #17039

Changed files

  • agent/auxiliary_client.py (modified, +36/-0)
  • agent/redact.py (modified, +6/-3)
  • gateway/run.py (modified, +10/-4)
  • hermes_cli/commands.py (modified, +3/-0)
  • hermes_cli/gateway.py (modified, +22/-8)
  • hermes_cli/main.py (modified, +18/-7)
  • hermes_cli/profiles.py (modified, +141/-2)
  • run_agent.py (modified, +33/-9)
  • tools/terminal_tool.py (modified, +10/-2)

Code Example

# Current code (run_agent.py ~L13108)
last_reasoning = None
for msg in reversed(messages):
    if msg.get("role") == "assistant" and msg.get("reasoning"):  # ← skips current msg when reasoning=None
        last_reasoning = msg["reasoning"]                        # ← matches older msg instead
        break

---

last_reasoning = None
for msg in reversed(messages):
    if msg.get("role") == "assistant":
        last_reasoning = msg.get("reasoning")  # None is the correct result when no reasoning
        break
RAW_BUFFERClick to expand / collapse

Bug Description

The last_reasoning extraction in run_conversation() walks backwards through the entire message history to find any assistant message with a truthy reasoning field. When the current turn produces no reasoning (e.g. the model returns reasoning_content: null for a simple prompt), the loop skips the current message and matches an older assistant message, displaying its reasoning as if it belonged to the current response.

Steps to Reproduce

  1. Use a model that returns reasoning_content for some turns but null for others (e.g. GLM-5, which outputs reasoning for complex prompts but returns null for simple ones like "hello")
  2. Send a complex message → model returns reasoning → reasoning is displayed correctly
  3. Send a simple message → model returns reasoning_content: nullthe previous turn's reasoning is incorrectly displayed again

Expected Behavior

If the current turn produced no reasoning, no reasoning should be displayed. The display should reflect the current turn's output, not a stale reasoning from a previous turn.

Root Cause

# Current code (run_agent.py ~L13108)
last_reasoning = None
for msg in reversed(messages):
    if msg.get("role") == "assistant" and msg.get("reasoning"):  # ← skips current msg when reasoning=None
        last_reasoning = msg["reasoning"]                        # ← matches older msg instead
        break

The and msg.get("reasoning") condition causes the loop to skip the last assistant message when its reasoning is None/falsy, continuing backwards until it finds any older message that has reasoning.

Fix

Stop at the last assistant message regardless of whether its reasoning field is populated:

last_reasoning = None
for msg in reversed(messages):
    if msg.get("role") == "assistant":
        last_reasoning = msg.get("reasoning")  # None is the correct result when no reasoning
        break

Impact

  • Affects all models that occasionally return reasoning_content: null (GLM-5, some DeepSeek/Qwen configurations, etc.)
  • Users see confusing/misleading reasoning that does not correspond to the current response
  • No crash or data loss, but degraded UX when reasoning display is enabled (show_reasoning: true)

extent analysis

TL;DR

Update the run_conversation() function to stop at the last assistant message regardless of its reasoning field content.

Guidance

  • Identify the run_agent.py file and locate the run_conversation() function around line 13108.
  • Modify the loop condition to break at the last assistant message, using msg.get("role") == "assistant" as the sole condition.
  • Update the last_reasoning assignment to use msg.get("reasoning"), which will correctly return None when the reasoning field is empty.
  • Verify the fix by testing with models that return reasoning_content: null for simple prompts, ensuring that no reasoning is displayed in such cases.

Example

last_reasoning = None
for msg in reversed(messages):
    if msg.get("role") == "assistant":
        last_reasoning = msg.get("reasoning")  # None is the correct result when no reasoning
        break

Notes

This fix assumes that the messages list contains all relevant conversation history and that the role and reasoning fields are correctly populated.

Recommendation

Apply the workaround by updating the run_conversation() function as described, to ensure correct reasoning display for models that return reasoning_content: null for simple prompts.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING