Skip to content

fix(config): retain last-known-good config when config.yaml fails to parse - #60591

Merged
teknium1 merged 1 commit into
mainfrom
codex-port/config-parse-last-known-good
Jul 9, 2026
Merged

teknium1 merged 1 commit into
mainfrom
codex-port/config-parse-last-known-good

Conversation

@teknium1

@teknium1 teknium1 commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

load_config() now retains the last successfully loaded config within a process when config.yaml fails to parse, instead of silently falling back to DEFAULT_CONFIG — which dropped every user override, including approvals.deny security rules that are documented to block commands even under --yolo.

Port of openai/codex#31188 ("Preserve managed exec policy after rules parse errors"). Codex's invariant: a parse failure in a policy-bearing config file must not replace the effective policy with an empty one. Their load_exec_policy_with_warning swapped the whole exec policy for Policy::empty() when a .rules file failed to parse, silently dropping managed prompt/forbidden rules. Hermes had the exact same bug shape in _load_config_impl().

The Hermes symptom: a long-running gateway whose user mid-edits config.yaml into broken YAML silently disarms their own approvals.deny rules (and every other override — fallback chain, aux providers, model settings) on the next load_config() cache miss. Reproduced live before the fix: with deny: ['curl*evil.com*'] loaded, corrupting the file mid-process made _match_user_deny_rule("curl http://evil.com/x") return None.

Changes

  • hermes_cli/config.py _load_config_impl(): on parse failure, if _LAST_EXPANDED_CONFIG_BY_PATH holds a previously loaded config for this path, keep serving it. The retained config is cached under the corrupt file's signature so the broken file isn't re-parsed on every load; fixing the file changes the signature and triggers a normal reload. Fresh processes with no last-known-good keep the existing DEFAULT_CONFIG fallback unchanged.
  • _warn_config_parse_failure(): new fallback= wording selector — the warning now says "Keeping the previously loaded config for this process — edits to config.yaml are being IGNORED until the YAML is fixed" when last-known-good is in play, instead of the misleading "Falling back to default config". Backup-to-.bak behavior unchanged.
  • 5 new tests in tests/hermes_cli/test_config.py (retention incl. deny rules, recovery after fix, fresh-process default fallback preserved, no re-warn spam on cached loads).

Adaptation notes

  • Codex preserves the managed policy layer specifically; Hermes' analog is the whole last-known-good merged config (user + managed), which _LAST_EXPANDED_CONFIG_BY_PATH already tracked for env-ref template preservation — no new state added.
  • Codex keeps parse errors fatal for strict clients (codex exec); Hermes has no strict-client analog — all callers get the warn-and-retain behavior.
  • save_config() stores the pre-expansion normalized dict in _LAST_EXPANDED_CONFIG_BY_PATH, so the retained value is passed through _expand_env_vars defensively (idempotent when already expanded).

Validation

Before After
deny rule after mid-process corruption None (rule silently dropped) still matches curl*evil.com*
fixed YAML after corruption reloads reloads (signature change busts retained cache)
fresh process + broken config DEFAULT_CONFIG DEFAULT_CONFIG (unchanged)
tests 149 passed test_config.py; 218 passed across config+deny-rule suites; 414 passed sibling _warn_config_parse_failure consumers

E2E (real imports, temp HERMES_HOME, real file I/O): 4/4 scenarios pass.

Infographic

config-parse-last-known-good

…parse

Port from openai/codex#31188: a parse failure in a policy-bearing config
file must not silently replace the effective policy with an empty/default
one. Codex's load_exec_policy_with_warning replaced the whole exec policy
with Policy::empty() when a .rules file failed to parse, silently dropping
managed prompt/forbidden rules; the fix preserves the managed policy while
still warning.

Hermes had the same bug shape in load_config(): a YAML parse error made
_load_config_impl() fall through to DEFAULT_CONFIG, dropping every user
override — including approvals.deny rules, which are documented to block
commands even under --yolo. In a long-running gateway, a user mid-editing
config.yaml into broken YAML silently disarmed their own deny rules on the
next load.

Now, when the process has a last successfully loaded config for that path
(_LAST_EXPANDED_CONFIG_BY_PATH), a parse failure keeps serving it (cached
under the corrupt file's signature so the broken file isn't re-parsed) and
the warning says edits are being ignored until the YAML is fixed. Fresh
processes with no last-known-good keep the existing DEFAULT_CONFIG
fallback and warning.

E2E-verified: deny rule 'curl*evil.com*' still blocks after mid-process
corruption; fixed file reloads normally; fresh-process fallback unchanged.
@alt-glitch alt-glitch added type/bug Something isn't working comp/cli CLI entry point, hermes_cli/, setup wizard area/config Config system, migrations, profiles sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades P2 Medium — degraded but workaround exists labels Jul 8, 2026
@teknium1
teknium1 merged commit fe25806 into main Jul 9, 2026
31 checks passed
@teknium1
teknium1 deleted the codex-port/config-parse-last-known-good branch July 9, 2026 23:36
santhreal pushed a commit to santhreal/hermes-agent that referenced this pull request Jul 13, 2026
…parse (NousResearch#60591)

Port from openai/codex#31188: a parse failure in a policy-bearing config
file must not silently replace the effective policy with an empty/default
one. Codex's load_exec_policy_with_warning replaced the whole exec policy
with Policy::empty() when a .rules file failed to parse, silently dropping
managed prompt/forbidden rules; the fix preserves the managed policy while
still warning.

Hermes had the same bug shape in load_config(): a YAML parse error made
_load_config_impl() fall through to DEFAULT_CONFIG, dropping every user
override — including approvals.deny rules, which are documented to block
commands even under --yolo. In a long-running gateway, a user mid-editing
config.yaml into broken YAML silently disarmed their own deny rules on the
next load.

Now, when the process has a last successfully loaded config for that path
(_LAST_EXPANDED_CONFIG_BY_PATH), a parse failure keeps serving it (cached
under the corrupt file's signature so the broken file isn't re-parsed) and
the warning says edits are being ignored until the YAML is fixed. Fresh
processes with no last-known-good keep the existing DEFAULT_CONFIG
fallback and warning.

E2E-verified: deny rule 'curl*evil.com*' still blocks after mid-process
corruption; fixed file reloads normally; fresh-process fallback unchanged.
justemu pushed a commit to justemu/hermes-agent that referenced this pull request Jul 18, 2026
…parse (NousResearch#60591)

Port from openai/codex#31188: a parse failure in a policy-bearing config
file must not silently replace the effective policy with an empty/default
one. Codex's load_exec_policy_with_warning replaced the whole exec policy
with Policy::empty() when a .rules file failed to parse, silently dropping
managed prompt/forbidden rules; the fix preserves the managed policy while
still warning.

Hermes had the same bug shape in load_config(): a YAML parse error made
_load_config_impl() fall through to DEFAULT_CONFIG, dropping every user
override — including approvals.deny rules, which are documented to block
commands even under --yolo. In a long-running gateway, a user mid-editing
config.yaml into broken YAML silently disarmed their own deny rules on the
next load.

Now, when the process has a last successfully loaded config for that path
(_LAST_EXPANDED_CONFIG_BY_PATH), a parse failure keeps serving it (cached
under the corrupt file's signature so the broken file isn't re-parsed) and
the warning says edits are being ignored until the YAML is fixed. Fresh
processes with no last-known-good keep the existing DEFAULT_CONFIG
fallback and warning.

E2E-verified: deny rule 'curl*evil.com*' still blocks after mid-process
corruption; fixed file reloads normally; fresh-process fallback unchanged.
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
…parse (NousResearch#60591)

Port from openai/codex#31188: a parse failure in a policy-bearing config
file must not silently replace the effective policy with an empty/default
one. Codex's load_exec_policy_with_warning replaced the whole exec policy
with Policy::empty() when a .rules file failed to parse, silently dropping
managed prompt/forbidden rules; the fix preserves the managed policy while
still warning.

Hermes had the same bug shape in load_config(): a YAML parse error made
_load_config_impl() fall through to DEFAULT_CONFIG, dropping every user
override — including approvals.deny rules, which are documented to block
commands even under --yolo. In a long-running gateway, a user mid-editing
config.yaml into broken YAML silently disarmed their own deny rules on the
next load.

Now, when the process has a last successfully loaded config for that path
(_LAST_EXPANDED_CONFIG_BY_PATH), a parse failure keeps serving it (cached
under the corrupt file's signature so the broken file isn't re-parsed) and
the warning says edits are being ignored until the YAML is fixed. Fresh
processes with no last-known-good keep the existing DEFAULT_CONFIG
fallback and warning.

E2E-verified: deny rule 'curl*evil.com*' still blocks after mid-process
corruption; fixed file reloads normally; fresh-process fallback unchanged.
leewenjie pushed a commit to leewenjie/hermes-agent that referenced this pull request Aug 7, 2026
…parse (NousResearch#60591)

Port from openai/codex#31188: a parse failure in a policy-bearing config
file must not silently replace the effective policy with an empty/default
one. Codex's load_exec_policy_with_warning replaced the whole exec policy
with Policy::empty() when a .rules file failed to parse, silently dropping
managed prompt/forbidden rules; the fix preserves the managed policy while
still warning.

Hermes had the same bug shape in load_config(): a YAML parse error made
_load_config_impl() fall through to DEFAULT_CONFIG, dropping every user
override — including approvals.deny rules, which are documented to block
commands even under --yolo. In a long-running gateway, a user mid-editing
config.yaml into broken YAML silently disarmed their own deny rules on the
next load.

Now, when the process has a last successfully loaded config for that path
(_LAST_EXPANDED_CONFIG_BY_PATH), a parse failure keeps serving it (cached
under the corrupt file's signature so the broken file isn't re-parsed) and
the warning says edits are being ignored until the YAML is fixed. Fresh
processes with no last-known-good keep the existing DEFAULT_CONFIG
fallback and warning.

E2E-verified: deny rule 'curl*evil.com*' still blocks after mid-process
corruption; fixed file reloads normally; fresh-process fallback unchanged.
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…parse (NousResearch#60591)

Port from openai/codex#31188: a parse failure in a policy-bearing config
file must not silently replace the effective policy with an empty/default
one. Codex's load_exec_policy_with_warning replaced the whole exec policy
with Policy::empty() when a .rules file failed to parse, silently dropping
managed prompt/forbidden rules; the fix preserves the managed policy while
still warning.

Hermes had the same bug shape in load_config(): a YAML parse error made
_load_config_impl() fall through to DEFAULT_CONFIG, dropping every user
override — including approvals.deny rules, which are documented to block
commands even under --yolo. In a long-running gateway, a user mid-editing
config.yaml into broken YAML silently disarmed their own deny rules on the
next load.

Now, when the process has a last successfully loaded config for that path
(_LAST_EXPANDED_CONFIG_BY_PATH), a parse failure keeps serving it (cached
under the corrupt file's signature so the broken file isn't re-parsed) and
the warning says edits are being ignored until the YAML is fixed. Fresh
processes with no last-known-good keep the existing DEFAULT_CONFIG
fallback and warning.

E2E-verified: deny rule 'curl*evil.com*' still blocks after mid-process
corruption; fixed file reloads normally; fresh-process fallback unchanged.
melon-xf added a commit to melon-xf/hermes-agent that referenced this pull request Sep 3, 2026
…parse (NousResearch#60591)

Port from openai/codex#31188: a parse failure in a policy-bearing config
file must not silently replace the effective policy with an empty/default
one. Codex's load_exec_policy_with_warning replaced the whole exec policy
with Policy::empty() when a .rules file failed to parse, silently dropping
managed prompt/forbidden rules; the fix preserves the managed policy while
still warning.

Hermes had the same bug shape in load_config(): a YAML parse error made
_load_config_impl() fall through to DEFAULT_CONFIG, dropping every user
override — including approvals.deny rules, which are documented to block
commands even under --yolo. In a long-running gateway, a user mid-editing
config.yaml into broken YAML silently disarmed their own deny rules on the
next load.

Now, when the process has a last successfully loaded config for that path
(_LAST_EXPANDED_CONFIG_BY_PATH), a parse failure keeps serving it (cached
under the corrupt file's signature so the broken file isn't re-parsed) and
the warning says edits are being ignored until the YAML is fixed. Fresh
processes with no last-known-good keep the existing DEFAULT_CONFIG
fallback and warning.

E2E-verified: deny rule 'curl*evil.com*' still blocks after mid-process
corruption; fixed file reloads normally; fresh-process fallback unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants