fix(claude): guard ClaudeConfig.notifyListeners against use-after-dispose

setProjectDir / refresh / ensureProbe each await disk I/O then call
notifyListeners(), but — unlike load(), which already guards — they didn't
re-check _disposed afterward. A project switch (or watcher refresh) racing the
config's disposal fires notifyListeners() on a disposed ChangeNotifier and
throws "used after disposed". Surfaced deterministically by the test deflake
(pumpEventQueue drains the async that Future.delayed(Duration.zero) was masking).
Add the same `if (_disposed) return;` guard to all three.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-14 23:13:01 +02:00
co-authored by Claude Opus 4.8
parent 04bb512cb7
commit 3dce5c614d
2 changed files with 9 additions and 0 deletions
+6
View File
@@ -73,6 +73,12 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit.
session persistence moved to `--resume` (D-77); the toolchain no longer probes
for it or warns when it's absent, on any platform.
### Fixed
- **`ClaudeConfig` no longer crashes on a project switch that races teardown.**
`setProjectDir` / `refresh` / `ensureProbe` now skip `notifyListeners()` if the
config was disposed during their async load (the guard `load()` already had).
## [2.4.1] — 2026-06-12
### Fixed
@@ -257,6 +257,7 @@ class ClaudeConfig extends ChangeNotifier {
if (probe == null) return; // stay on the static fallback
_probe = probe;
await _writeProbeCache(probe);
if (_disposed) return; // a slow probe racing a teardown mustn't notify a disposed notifier
notifyListeners();
} finally {
_probing = false;
@@ -268,6 +269,7 @@ class ClaudeConfig extends ChangeNotifier {
/// not re-resolved (the binary doesn't change under us at runtime).
Future<void> refresh() async {
await _loadDiskConfig();
if (_disposed) return; // a watcher-driven refresh racing a teardown mustn't notify a disposed notifier
notifyListeners();
}
@@ -278,6 +280,7 @@ class ClaudeConfig extends ChangeNotifier {
_stopWatching();
_projectDir = dir;
await _loadDiskConfig();
if (_disposed) return; // a project switch racing a teardown mustn't notify a disposed notifier
_startWatchers();
notifyListeners();
}