fix(issue-45): review round 2 - an unrunnable config check is not a pass, odd pids never close a pane, nan does not drop settings

codex round 2 (PR #48).

- R1: when `herdr config check` could not run (timeout, OSError), the
  merged config was handed to the server as checked. A merge with a schema
  error makes herdr drop the whole file, restore back on - the #45 failure.
  The merged body is no longer used unchecked: _write_checked falls to the
  fixed MINIMAL config with a warning. When the check cannot run on MINIMAL
  either, MINIMAL is still used (with its own warning) rather than no
  config: returning none would leave restore on, and MINIMAL is a fixed
  two-line file the real-herdr test accepts. Unknown non-zero exits stay as
  they were - herdr exits non-zero for harmless warnings (an unknown
  section) that do not drop the file.
- R2: _shell_in_foreground compared shell_pid and the foreground group with
  ==, so 0/0, False/False, ""/"" or -1/-1 closed the pane. Both must now be
  positive ints (bool excluded) before they are compared.
- R3: the re-parse check used ==, and nan != nan, so a valid TOML nan threw
  away every other user setting even on Python 3.11. _same_doc compares
  documents recursively with nan equal to nan.

Tests: R1 (timeout, OSError) and R3 (top-level, array, nested nan) in
test_issue45_herdr_config.py; R2 in the new tests/test_issue45_restore_unit.py
(a herdr stub: zero, false, empty, negative, null, string, true, float;
missing info; a real shell closed; a busy pane kept). On the previous code
12 of them fail. Python 3.11: 67 passed; Python 3.9: 33 passed / 34 skipped
(tomllib-only). Real-herdr restore tests and related files on 3.9: 197
passed / 1 skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
This commit is contained in:
GodopuandClaude Opus 5.5 committed 2026-10-05 17:50:15 +09:00
1 parent e8d9151226
commit ab93776d6d
4 files changed
+125 -4

No files matched your search

+38
View File
@@ -380,3 +380,41 @@ def test_a_foreign_file_with_the_output_name_is_kept(tmp_path):
other_path, _ = hc.write_server_config({"HERDR_CONFIG_PATH": str(other)})
assert other_path not in (path, str(foreign))
assert hc.user_config_path({"HERDR_CONFIG_PATH": path}) == str(source)
# --- review round 2 (codex R1, R3) -------------------------------------------
@needs_tomllib
@pytest.mark.parametrize("exc", [subprocess.TimeoutExpired(["herdr", "config", "check"], 30),
OSError("herdr went away")], ids=["timeout", "oserror"])
def test_an_unrunnable_check_never_hands_over_an_unchecked_merge(tmp_path, monkeypatch, exc):
"""R1: a check that cannot run is not a pass. A merge with a schema error
would make herdr drop the whole file (restore back on), so the result is
the fixed minimal config, with a warning - never the unchecked merge."""
source = tmp_path / "config.toml"
source.write_text('[ui]\nconfirm_close = "invalid-bool"\n')
def boom(path, herdr):
raise exc
monkeypatch.setattr(hc, "herdr_rejects", boom)
path, warnings = hc.write_server_config({"HERDR_CONFIG_PATH": str(source)}, herdr="/fake/herdr")
assert path is not None
text = Path(path).read_text()
assert "invalid-bool" not in text and "confirm_close" not in text, text
assert tomllib.loads(text) == {"session": {"resume_agents_on_restore": False}}
assert any("not using an unchecked merge" in w for w in warnings), warnings
assert source.read_text() == '[ui]\nconfirm_close = "invalid-bool"\n'
@needs_tomllib
@pytest.mark.parametrize("nan_part", ["[custom]\nvalue = nan\n", "[custom]\nvalues = [1.0, nan, -nan]\n",
"[custom.deep]\nt = { a = nan }\n"], ids=["top", "array", "nested"])
def test_a_nan_value_does_not_drop_the_other_settings(nan_part):
"""R3: nan is valid TOML and never == itself; the re-parse check must treat
it as equal, or the user's other settings are thrown away."""
body, warnings = hc.server_config_text("[ui]\nconfirm_close = false\n" + nan_part)
assert warnings == [], warnings
parsed = tomllib.loads(body)
assert parsed["ui"]["confirm_close"] is False
assert parsed["session"]["resume_agents_on_restore"] is False
assert "custom" in parsed