Skip to content

feat(check_config): report a config file that exists but is not in use - #414

Open
PYDuquesnoy wants to merge 1 commit into
masterfrom
fix/410-check-config-reports-an-ignored-file
Open

PYDuquesnoy wants to merge 1 commit into
masterfrom
fix/410-check-config-reports-an-ignored-file

Conversation

@PYDuquesnoy

Copy link
Copy Markdown
Contributor

Addresses the first checkbox of #410. It deliberately does not decide the precedence — that is
the second checkbox and it is yours.

What was reported

Registered at user scope with IRIS_HOST/IRIS_WEB_PORT, with a .iris-agentic-dev.toml in the
project folder, a fresh session reports:

connection_source: explicit_flag
config_file:       null
config_watch_path: /proj/.iris-agentic-dev.toml     <- a real file, being ignored

Three true fields, and nothing joining them.

The file is invisible, not merely outranked

apply_workspace_config_with_path short-circuits before the file is read:

if explicit.is_some() {
    // A CLI flag outranks the file, so a broken file is not consulted and not fatal. This is the
    // deliberate escape hatch: an explicit --host gets you moving without editing the file.
    return Ok((explicit, None));
}

It returns None for the path, so the connection never learns the file exists — which is why
config_file is null rather than naming the file it declined. The warning therefore has to be built
from the watcher's path plus a filesystem read, not from anything on the connection.

The half that actually bites

An edit to that same ignored file is adopted on the next tool call, because check_reload
calls the raw load_workspace_config directly and that loader takes no explicit argument, so it
cannot honour the short-circuit. Whether a session reaches the flag's instance or the file's therefore
depends on whether the file happened to be touched after startup — which is what made the reporter
switch to --mcp-config --strict-mcp-config to get determinism.

The message says both halves. A warning that only said "your file is being ignored" would leave the
reader concluding the file is inert and editing it freely, which is the trap.

It is the complement of a warning that already exists

check_config already warns for config_file.is_none() && !is_explicit — fallback discovery. That is
one half of a partition on is_explicit, and only that half had a message. So this is the same shape
as #409 and #408: a guard on one sibling and not its twin.
the_two_warnings_are_not_interchangeable asserts the two messages cannot be confused, and
the_warning_does_not_claim_the_file_is_absent asserts this one never borrows the other's diagnosis —
the file is sitting right there.

The precedence is left open, on purpose

Honouring the escape hatch on reload would be one line, and it would break the other documented
behaviour: check_config's own description promises hot-reload as an operator feature, and the server
is normally registered with IRIS_HOST, which is explicit_flag. So the two documented behaviours
contradict each other whenever both a flag and a file are present, and picking a winner is a contract
call. the_warning_does_not_pick_a_precedence asserts the message describes the asymmetry without
declaring either side correct — a message that implied one would document a contract nobody chose.

The wiring is tested, not just the string

The branch condition is extracted as ignored_config_path(config_file, source_is_explicit, watch_path), so the three reasons to stay silent are unit-testable rather than living inline in an
async handler: the file IS the source; the source is not explicit (that is the other branch's case);
nothing exists at the watched path (then the watcher is waiting, and claiming a file is ignored would
be a fabrication).

the_existence_check_is_not_inert runs the same call three times, differing only in whether the file
is on disk — absent → None, created → Some, removed → None. Without the negative halves it would
pass against an implementation that reports a file which is not there, which is the mirror image of
the defect. The check is is_file(), not exists(), because exists() is true for a directory.

Verification

  • fmt --check rc=0, this file 13/13, cargo clippy --workspace --all-targets -- -D warnings
    rc=0, cargo build --workspace rc=0
  • cargo test --workspace with the IRIS env unset rc=0 — 1855 passed, 0 failed, 69 ignored across
    66 binaries
    (66 test result: lines, so the total is measured and not an empty grep)
  • Mutation check: 14 mutants over the 13 assertions, 14/14 killed by the predicted assertion, file
    restored byte-identical (sha checked), post-restore baseline green

Two mutants are worth naming because they cover the ways this could be wrong rather than absent:
reporting without checking the file exists (killed by the_existence_check_is_not_inert), and the
message implying the flag is the correct one to rely on (killed by
the_warning_does_not_pick_a_precedence).

🤖 Generated with Claude Code

https://claude.ai/code/session_01N7fLbq3ftb82ub45QCpsPB

#410 first half. Registered with IRIS_HOST/IRIS_WEB_PORT and a .iris-agentic-dev.toml in
the project folder, check_config reported connection_source: explicit_flag,
config_file: null and a config_watch_path pointing straight at that file. Three true
fields and nothing joining them.

The file is invisible rather than merely outranked: apply_workspace_config_with_path
short-circuits BEFORE the file is read and returns None for the path, so the connection
never learns it exists. The warning therefore comes from the watcher's path plus a
filesystem read, not from the connection.

And the half that bites: an EDIT to that same ignored file IS adopted on the next tool
call, because check_reload calls the raw load_workspace_config, which takes no
`explicit` argument and cannot honour the short-circuit. So whether a session reaches
the flag's instance or the file's depends on whether the file was touched after startup.

This is the complement of an existing warning. check_config already covers
config_file.is_none() && !is_explicit (fallback discovery); that is one half of a
partition on is_explicit and only that half had a message.

Which precedence the fork should adopt is still open, and this does not decide it — the
message describes the asymmetry without declaring either side correct, asserted by
the_warning_does_not_pick_a_precedence.

The branch condition is extracted as ignored_config_path() so the WIRING is testable and
not only the message; the_existence_check_is_not_inert runs the same call with the file
absent, created and removed again, because without the negative halves it would pass
against an implementation that reports a file which is not there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7fLbq3ftb82ub45QCpsPB
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant