obscure --fail-on-change error
Describe the bug
I am trying to enable treefmt on CI but I find the behavior elusive: here are 2 consecutive runs:
[teto]$ (nix)treefmt --fail-on-change
WARN format: no formatter for path: .gitlab-ci.yml
treefmt: error: unexpected changes detected, --fail-on-change is enabled
[teto]$ (nix)treefmt --fail-on-change
traversed 4110 files
emitted 0 files for processing
formatted 0 files (0 changed) in 34ms
- Why is it failing on the first run ? is it because of
WARN format: no formatter for path: .gitlab-ci.ymlor another path not displayed ? I dont think it should fail when a file is not covered by a formatter, it makes the out of the box behavior quite annoying. - why it doesn't fail on the second run ? I haven't done anything between the 2 runs
Expected behavior
System information
treefmt v2.0.3
Additional context
I tried to convert warnings into debug in case the WARN format ... was making the check fail, which gives:
$ treefmt --fail-on-change -udebug --no-cache
treefmt: error: unexpected changes detected, --fail-on-change is enabled
I guess it fails because of the same reasons than in my initial issue but now I have little feedback to address the issue.
@teto if you add -v, -vv you will get info and debug level logging.
It's recommended to use --fail-on-change with --no-cache. There's a --ci flag which has been available since 2.0.4 that does a few things that ensure a better experience in a CI environment https://treefmt.com/usage#ci.
I recommend upgrading to 2.0.5, which is in nixpkgs unstable now https://nixpkgs-tracker.ocfox.me/?pr=336307.
If you use treefmt-nix, I've just created a PR to update the default version to 2.0.5. It currently uses 2.0.4. https://github.com/numtide/treefmt-nix/pull/229
if you add -v, -vv you will get info and debug level logging.
I think people are more used to the inverse logic, ie. the tool gives useful output by default. For the minority who want a quiet output, they can redirect to /dev/null or use a --quiet flag. Adding -vv I could see:
...
INFO format | just: 1 file(s) processed in 25.524693ms
DEBU format: file has changed path=/home/teto/nova/justfile prev_size=2168 current_size=2168 prev_mod_time="2024-08-30 10:50:11 +0200 CEST" current_mod_time="2024-09-02 12:41:41 +0200 CEST"
I ran a simple treefmt, reran treefmt --fail-on-change which now succeeds, yet git diff/git status shows no difference. I tried to check if I had a git config to ignore whitespace change but couldn't find any. Does it mean it just refreshed the cache but the actual file was correctly formatted from the start ?
So the WARN format: no formatter for path: .gitlab-ci.yml were red herrings that did not cause the failure. I am shown information I dont care but the one that could help me fix the issue is hidden :(
If you use treefmt-nix, I've just created a PR to update the default version to 2.0.5. It currently uses 2.0.4
I see it's merged I will try to update ty !
It's recommended to use --fail-on-change with --no-cache. There's a --ci flag which has been available since 2.0.4 that does a few things that ensure a better experience in a CI environment https://treefmt.com/usage#ci.
I think you're seeing the same issue as https://github.com/numtide/treefmt/issues/365. Moving to 2.0.5 is the fix.
So the WARN format: no formatter for path: .gitlab-ci.yml were red herrings that did not cause the failure. I am shown information I dont care but the one that could help me fix the issue is hidden :(
2.0.5 now logs at error level the file has changed entry when --fail-on-change is enabled, making it clearer by default what happened.
I think people are more used to the inverse logic, ie. the tool gives useful output by default. For the minority who want a quiet output, they can redirect to /dev/null or use a --quiet flag.
I disagree that there is a minority wanting quiet output by default. Regardless, this is in keeping with v1 behaviour, if not a little less noisy by default.
In general, I want to stabilise the move to Go with 2.0.x. Once that happens (which I feel is pretty close), I'm happy to start diverging more clearly with 2.1.x.
Hi, any updates on this ?
I am using version 2.2.0 and I am having a similar problem.
On my local machine it works:
$ treefmt --ci --no-cache -v
INFO ci mode enabled
INFO formatter | deno: 1024 file(s) processed in 113.430612ms
INFO formatter | taplo: 10 file(s) processed in 5.297222ms
INFO formatter | deno: 280 file(s) processed in 34.173641ms
INFO formatter | rustfmt: 427 file(s) processed in 281.510984ms
traversed 97926 files
emitted 1741 files for processing
formatted 1741 files (0 changed) in 1.012s
But on my CI pipeline, which uses the same treefmt version (I am sure because I'm using a custom docker image), I get the following:
$ treefmt --ci --no-cache -v
INFO ci mode enabled
INFO formatter | taplo: 10 file(s) processed in 6.612497ms
INFO formatter | deno: 226 file(s) processed in 66.259717ms
ERRO file has changed path=apps/labifront/src/static/error-pages/401.html prev_size=806 prev_mod_time="2025-07-15 13:29:12 +0000 UTC" current_size=791 current_mod_time="2025-07-15 13:29:14 +0000 UTC"
INFO formatter | rustfmt: 427 file(s) processed in 313.749761ms
traversed 721 files
emitted 663 files for processing
formatted 663 files (1 changed) in 1.14s
Error: unexpected changes detected, --fail-on-change is enabled
These 2 commands are running on the same commit. I have configured line endings to LF with git. I don't know what else could be causing this.
Plus, when I try to run this another time on the same job using an after_script in Gitlab CI, it works!!!
Running after script...
$ if [ "$CI_JOB_STATUS" != "success" ]; then treefmt --ci --no-cache -v; fi
INFO ci mode enabled
INFO formatter | taplo: 10 file(s) processed in 16.523344ms
INFO formatter | deno: 226 file(s) processed in 40.070699ms
INFO formatter | rustfmt: 427 file(s) processed in 323.135274ms
traversed 721 files
emitted 663 files for processing
formatted 663 files (0 changed) in 422ms
For reference, here is my treefmt.toml
# The formatter multiplexer - https://github.com/numtide/treefmt
on-unmatched = "debug"
walk = "git"
excludes = ["target/*", "node_modules/*", ".moon/cache/*"]
fail-on-change = true
[formatter.deno]
command = "deno"
excludes = []
includes = [
"*.css",
"*.html",
"*.js",
"*.json",
"*.jsonc",
"*.jsx",
"*.less",
"*.markdown",
"*.md",
"*.sass",
"*.scss",
"*.ts",
"*.tsx",
"*.yaml",
"*.yml",
]
options = ["fmt"]
[formatter.rustfmt]
command = "rustfmt"
excludes = []
includes = ["*.rs"]
options = ["--config", "skip_children=true", "--edition", "2024"]
[formatter.taplo]
command = "taplo"
excludes = []
includes = ["*.toml"]
options = ["format"]
Hi, just wanted to follow up.
I was able to resolve this by upgrading the Deno version used in CI. I’m not exactly sure why it fixed the issue, but once Deno was bumped, the unexpected file change error stopped happening. (Deno is the formatter I'm using for the html file).
I suspect Deno was badly behaved like we've seen with Just in the past. Some formatters are manipulating the modtime, but I've seen they can truncate it for example.
I think this has been resolved, so I'm marking it closed for now.