fix(env): improve hook-env watch_files tracking and early-exits#8716
fix(env): improve hook-env watch_files tracking and early-exits#8716
Conversation
Env plugins (MiseEnv modules) can return watch_files to monitor for changes, but these were never reaching the hook-env session or env cache. This meant modifying a watched file (e.g. a secrets config) wouldn't trigger re-evaluation until the next config change. Four changes: 1. config.watch_files() now includes env_results.watch_files from NonToolsOnly plugins, so the slow-path check detects changes. 2. env_with_path_and_split() returns tool_watch_files so hook-env can merge them into the session for the fast-path check. 3. final_env() merges NonToolsOnly watch_files into the ToolsOnly env_results, so save_env_cache_split() can store both in CachedEnv. 4. cli/hook_env.rs chains PREV_SESSION.watch_files into the slow-path check to cover tools=true plugin files that aren't in config.watch_files(). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses a critical issue where environment plugin Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
…w session The slow-path check was extending watch_files in-place, which caused removed plugin watch_files to persist indefinitely across sessions. Use a separate variable for the slow-path so only current watch_files are stored in the new session. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
279e545 to
f128952
Compare
Add e2e test verifying that env plugin watch_files trigger hook-env re-evaluation when the watched file changes, including env cache invalidation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
f128952 to
10283f8
Compare
There was a problem hiding this comment.
Code Review
This pull request effectively addresses the issue of watch_files from env plugins not being tracked in the session or env cache. The changes correctly integrate plugin-defined watch_files into the config.watch_files() chain and the HookEnvSession, ensuring that modifications to these files trigger re-evaluation. The addition of a comprehensive E2E test covering all four cache/tools combinations is excellent and provides strong validation for the fix. The code is clear, well-structured, and directly targets the identified gaps, leading to a robust solution.
Greptile SummaryThis PR fixes a family of related hook-env stability bugs: env plugin Key changes:
Confidence Score: 5/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[hook-env run] --> B{env_cache enabled?}
B -- yes --> C{try_load_env_cache_full}
C -- hit --> D[watch_files = cached.watch_files]
C -- miss --> E[config.watch_files incl. tools=false plugin WF]
B -- no --> E
D & E --> F[slow_path_watch_files = watch_files ∪ PREV_SESSION.watch_files]
F --> G{should_exit_early?}
G -- yes --> H[return early]
G -- no --> I[env_with_path_and_split]
I --> J{cache hit?}
J -- hit --> K[env_watch_files = cached.watch_files]
J -- miss --> L[final_env: load_post_env tools=true\n+ merge NonToolsOnly WF]
L --> M[env_watch_files = env_results.watch_files]
K & M --> N[session watch_files = watch_files ∪ env_watch_files]
N --> O[build_session: latest_update = max of\nall watch file mtimes\n+ data dir mtime\n+ config search dir mtimes]
O --> P[__MISE_SESSION written]
P --> Q[Next prompt: fast-path stable]
Reviews (5): Last reviewed commit: "fix(env): align hook-env session timesta..." | Re-trigger Greptile |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| @@ -0,0 +1,159 @@ | |||
| #!/usr/bin/env bash | |||
There was a problem hiding this comment.
That's partially related to #8716 (comment), though even when considering the sleeps, I'm not sure why it's taking long enough to pass the 20 second warning. I'll take a look.
There was a problem hiding this comment.
ideally we don't need to mark it as slow so the test will actually run on this PR
There was a problem hiding this comment.
I think I can improve this a bit.
With debug builds, mise activate takes ~2.5s and mise hook-env takes ~1.3s. We also have the one second sleeps for file modification detection. Since we're testing four scenarios, (2.5 + 1.3 + 1.0) * 4 = 19.2s, and that's before we consider anything else (like macOS Gatekeeper randomly verifying the executable, since a build is performed right before the test).
If I configure both tools=true and tools=false plugins in the same config and test them simultaneously in each scenario, I can avoid running activate as many times and likely get it under the 20s. I'm also not sure if I even need to run activate more than once.
I'll work on that now.
There was a problem hiding this comment.
ah hmm, I didn't realize things were so slow. I think maybe what we could do is loosen the rules on what is considered slow to more like 1 minute. It's really to avoid things that take multiple minutes like compiling ruby.
I think it would also be fine to just have a larger test that just activates once. We do that often.
There was a problem hiding this comment.
I'm having some troubles with my test being a bit flaky, but I think it may be interacting with a pre-existing mise.lock issue that’s reproducible on main:
- Temporarily initialize the logger before
should_exit_early_fast()so fast-path trace logs are visible. export MISE_TRACE=1cdinto a directory with amise.tomlbut nomise.lock- Press Enter a few times to repeatedly trigger
hook-env, and observe thatshould_exit_early()keeps running and returningfalsebecausemise.lockis treated as a watched file that was "deleted", even though it never existed. touch mise.toml- Press Enter a few more times, and observe that
hook-envnow exits early as expected andmise.lockis no longer present in the resolved watch files.
I don’t yet understand why touching mise.toml changes whether mise.lock appears in the watch set, but the current behavior seems wrong regardless: a missing optional mise.lock should not keep hook-env from stabilizing.
My test seems to hit this sometimes as well, which may explain the flakiness. I haven’t pinned down exactly what makes it appear or disappear yet, but once mise.lock is no longer treated as a required existing watched file, I suspect the test will become stable too.
As a potential fix, I tried ignoring mise.lock if it didn't exist. That improved things so that when you cd into a directory, the fast check allows for an early exit on subsequent prompts. However, if I then touch mise.lock, it starts falling back to the slow check again.
I'm seeing if I can identify what's causing this to happen. It's maybe a bit out-of-scope at this point, but I don't want to introduce a flaky test.
There was a problem hiding this comment.
Alright, I think I have that mise.lock issue resolved. I tried to fix it in a separate PR but then that e2e test ran into the same problem being fixed in this PR, so I figured I'd just group the fixes together in this PR. Let me know if there are any concerns about the size of the PR though.
The 4th tuple element from env_with_path_and_split() has different content on cache hit (full CachedEnv.watch_files set) vs cache miss (only plugin watch_files). Rename to env_watch_files and document the asymmetry to avoid confusing future contributors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Test both tools=true and tools=false plugins simultaneously in each scenario instead of separately, cutting from 4 sequential runs to 2. This roughly halves the test duration (27s → 16s) by reducing the number of mise invocations while still covering all four combinations. Also adds a settle step (extra hook-env after activate) to handle cache mode transitions between scenarios, and drops the _slow suffix since the test now fits within the 20s threshold. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
mise.lock was unconditionally added to the watch set even when the file didn't exist. The fast path sometimes treated it as perpetually deleted, preventing hook-env from stabilizing in projects without a lockfile. - Only watch mise.lock when it exists; parent dir mtime catches creation - Include mise.lock in env-cache key for proper invalidation - Add e2e test for optional mise.lock hook-env behavior Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
d182e97 to
b4553ee
Compare
Include the same data-dir and config-search directory mtimes in hook-env session latest_update that should_exit_early_fast() checks. This lets a successful slow-path run stabilize subsequent prompts instead of repeatedly falling back because directory mtimes remain newer than the session timestamp.
b4553ee to
06840da
Compare
### 🐛 Bug Fixes - **(env)** improve hook-env watch_files tracking and early-exits by @rpendleton in [#8716](#8716) - **(install)** create runtime symlinks in system/shared install directories by @jdx in [#8722](#8722) - apply --silent flag to global settings to suppress output by @nkakouros in [#8720](#8720) ### 📦️ Dependency Updates - ignore RUSTSEC-2026-0066 astral-tokio-tar advisory by @jdx in [#8723](#8723) ### 📦 Registry - add acli by @ggoggam in [#8721](#8721) ### New Contributors - @rpendleton made their first contribution in [#8716](#8716) - @ggoggam made their first contribution in [#8721](#8721) ## 📦 Aqua Registry Updates #### Updated Packages (1) - [`astral-sh/ty`](https://github.com/astral-sh/ty)
Summary
Fixes #8603 — env plugins (
MiseEnvmodules) can returnwatch_files, but those files were never tracked in the hook-env session or env cache. Modifying a watched file (e.g. a secrets config) wouldn't trigger re-evaluation until the next config change or directory switch.Additionally fixes two related hook-env stability issues discovered while writing e2e tests for the original issue:
Plugin watch_files tracking (4 gaps)
config.watch_files()now chainsenv_results.watch_files, sotools=falseplugin watch_files reach the slow-path check and session.env_with_path_and_split()now returnstools=trueplugin watch_files so hook-env can merge them into the session for the next prompt's fast-path check.PREV_SESSION.watch_filesto detect changes totools=trueplugin files beforeload_post_envruns. Uses a separate variable so stale entries don't persist indefinitely.final_env()mergesNonToolsOnlywatch_files intoToolsOnlyenv_results, soCachedEnvtracks all plugin watch_files and invalidates correctly.Missing mise.lock destabilizes hook-env
Projects without a
mise.lockfile could fail to stabilize because the nonexistent lockfile was unconditionally added to the watch set and sometimes treated as perpetually deleted, preventingshould_exit_earlyfrom returning true.mise.lockto the watch set when it actually exists; parent directory mtime checks already detect later creation.mise.lockpaths in the env-cache key so lockfile creation, deletion, or modification properly invalidates cached env state.Session timestamp doesn't account for directory mtimes
should_exit_early_fast()checks config-search directory mtimes and the data-dir mtime, butbuild_session()didn't include those inlatest_update. After a successful slow-path run, subsequent prompts would repeatedly fall back to the slow path because directory mtimes remained newer than the session timestamp.config_search_dir_mtimes()helper shared by bothshould_exit_early_fast()andbuild_session().latest_updateso a full run stabilizes subsequent prompts.Test plan
test_env_plugin_watch_files) covers all four plugin watch_files code paths:tools=false+cache=offtools=false+cache=ontools=true+cache=offtools=true+cache=ontest_env_lockfile_caching) verifies missing lockfile doesn't prevent stabilization, and that creation/removal invalidates exactly once before re-stabilizingtest_hook_env_dir_mtime_stabilizes) verifies hook-env stabilizes after a directory mtime change🤖 Generated with Claude Code