1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
//! `tm doctor` auto-memory probe and its `--fix` repair (#7685).
//!
//! Why: the owner directive is that Claude Code's own auto memory (`MEMORY.md`)
//! is not used in tm sessions — `trusty-memory` is the memory. Two write sites
//! make that true at launch (the project-tier `autoMemoryEnabled: false` key and
//! the `CLAUDE_CODE_DISABLE_AUTO_MEMORY=1` managed-spawn assignment), and
//! neither is observable afterwards: a project that has never been launched, or
//! whose settings file an operator edited, silently carries auto memory back on
//! with nothing reporting it. Measured on 2026-09-12, three headless subagent
//! probes in this project each read the auto-memory index and quoted its first
//! entry, so the exposure is real rather than theoretical.
//!
//! The 2026-09-12 owner ruling made auto memory a FALLBACK rather than a thing
//! to be off unconditionally, which is why this row grades a PAIR: the same
//! configuration is a defect beside a healthy trusty-memory and correct beside a
//! dead one — and auto memory off beside a dead one is the project having no
//! memory at all.
//! What: [`check_auto_memory`] resolves the EFFECTIVE `autoMemoryEnabled` value
//! across the same four settings layers Claude Code itself consults — project
//! local > project > user local > user — reads whether the project's `MEMORY.md`
//! still holds facts, and grades the pair against whether trusty-memory
//! answered. Its doc carries the full table. [`repair_auto_memory`] is the
//! `tm doctor --fix` half: it writes the key into the PROJECT tier through the
//! same [`crate::core::session_launch::merge_settings_key`] the launch path
//! uses, so a repaired project and a launched one converge on one code path —
//! and in the same DIRECTION, `false` beside a healthy trusty-memory and `true`
//! beside a dead one. It does NOT migrate — `tm memory import-auto-memory` does,
//! and the check names it.
//! Test: `auto_memory_fails_when_on_beside_a_healthy_trusty_memory`,
//! `auto_memory_is_ok_when_on_while_trusty_memory_is_down`,
//! `auto_memory_warns_when_off_while_trusty_memory_is_down`,
//! `auto_memory_ok_when_off_and_the_index_is_empty`,
//! `auto_memory_fails_when_the_index_still_holds_facts`,
//! `auto_memory_warns_when_the_index_holds_facts_and_memory_is_down`,
//! `auto_memory_project_local_overrides_project`,
//! `auto_memory_falls_back_to_the_user_tier`,
//! `auto_memory_fails_on_malformed_json`,
//! `auto_memory_repair_applies_when_absent`,
//! `auto_memory_repair_restores_the_fallback_when_memory_is_down`,
//! `auto_memory_repair_dry_run_writes_nothing`,
//! `auto_memory_repair_is_silent_when_already_false`,
//! `auto_memory_repair_reports_a_write_failure`,
//! `auto_memory_repair_never_migrates`,
//! `auto_memory_names_a_wedged_trusty_memory_apart_from_a_dead_one`.
use ;
use crateClaudeConfigReader;
use crate;
use crate;
use crateMemoryReachability;
use crate;
/// The `tm doctor` row this module produces.
pub const CHECK_NAME: &str = "auto_memory";
/// The documented env-var half of the same directive.
///
/// Why: named in this check's remedy text so an operator reading a `Warn` row
/// learns both levers, not only the one `--fix` writes. The assignment itself
/// lives in [`crate::runtime::claude_code::env_bin_prefix`]; this is prose.
/// What: the variable name, as Claude Code's docs spell it.
/// Test: `auto_memory_fails_when_on_beside_a_healthy_trusty_memory`.
const DISABLE_ENV_VAR: &str = "CLAUDE_CODE_DISABLE_AUTO_MEMORY";
/// What one settings layer says about `autoMemoryEnabled`.
///
/// Why: "the file is absent", "the file exists and is silent on the key" and
/// "the file is malformed" are three different facts. The first two fall
/// through to the next layer identically; the third must stop resolution and
/// report, because a settings file Claude Code cannot parse is a problem this
/// check would otherwise hide.
/// What: `Set` carries the boolean, `Silent` means keep looking, `Broken`
/// carries the reason.
/// Test: exercised by every `auto_memory_*` check test.
/// Read `autoMemoryEnabled` out of one settings file.
///
/// Why: shared by every layer in [`resolution_layers`] so all four apply
/// identical parsing rules.
/// What: `Silent` for a missing file, a non-object body, an absent key, or a
/// key whose value is not a boolean; `Broken` for an unreadable file or invalid
/// JSON; `Set` otherwise.
/// Test: `auto_memory_fails_on_malformed_json`,
/// `auto_memory_fails_when_unset_beside_a_healthy_trusty_memory`.
/// The settings layers Claude Code consults, highest precedence first.
///
/// Why: the same chain `daemon::doctor_output_style` walks, for the same reason
/// — a `settings.local.json` that sets the key outranks the project tier tm
/// writes, so reading only the project tier would report a value Claude Code
/// never uses.
/// What: project-local, project, user-local, user; the project pair is omitted
/// when the caller has no project directory. Each entry carries a scope label
/// for the report.
/// Test: `auto_memory_project_local_overrides_project`,
/// `auto_memory_falls_back_to_the_user_tier`.
/// The effective `autoMemoryEnabled` across every layer Claude Code consults.
///
/// Why: three of the four verdicts depend on one fact — is auto memory ON right
/// now — and a malformed settings file is a fourth outcome that has to stop
/// resolution rather than fall through as "unset".
/// What: `Err(reason)` for the first unparseable layer; otherwise
/// `Ok(Some((value, scope, path)))` for the first layer that sets the key, and
/// `Ok(None)` when none does.
/// Test: every `auto_memory_*` check test.
type Effective = ;
/// Probe the auto-memory FALLBACK posture for this project (#7685).
///
/// Why: the owner ruling of 2026-09-12 made this a check about the FALLBACK
/// POSTURE rather than about one boolean. Auto memory being on is a finding only
/// while trusty-memory is UP; while it is down, auto memory on is the posture the
/// directive asks for, and auto memory OFF is the double-loss state — a project
/// with no memory at all — which is exactly what an operator needs told.
/// What: resolves the effective key via [`effective_setting`] and reads the
/// project's `MEMORY.md` through
/// [`crate::core::auto_memory_import::index_has_content`], then:
///
/// | trusty-memory | auto memory | `MEMORY.md` | verdict |
/// |---|---|---|---|
/// | up | on | any | `Fail` — the directive is unmet |
/// | up | off | non-empty | `Fail` — facts are stranded in the fallback |
/// | up | off | empty/absent | `Ok` |
/// | down | on | any | `Ok` — the fallback is carrying the project |
/// | down | off | any | `Warn` — no memory at all; `--fix` restores it |
///
/// An unset key counts as ON: Claude Code documents auto memory as on by
/// default. A malformed settings file is `Fail` regardless. Read-only —
/// [`repair_auto_memory`] is the write half, and it never migrates.
/// Test: `auto_memory_fails_when_on_beside_a_healthy_trusty_memory`,
/// `auto_memory_is_ok_when_on_while_trusty_memory_is_down`,
/// `auto_memory_ok_when_off_and_the_index_is_empty`,
/// `auto_memory_fails_when_the_index_still_holds_facts`,
/// `auto_memory_warns_when_off_while_trusty_memory_is_down`,
/// `auto_memory_warns_when_the_index_holds_facts_and_memory_is_down`,
/// `auto_memory_project_local_overrides_project`,
/// `auto_memory_falls_back_to_the_user_tier`,
/// `auto_memory_fails_on_malformed_json`,
/// `auto_memory_names_a_wedged_trusty_memory_apart_from_a_dead_one`.
pub
/// Whether this project's auto-memory index still holds anything.
/// Read the project's auto-memory index state.
///
/// Why: the index is the other half of the directive — a project whose key says
/// `false` while `MEMORY.md` still carries facts has those facts in neither
/// memory. The path resolution is
/// [`crate::core::auto_memory_import::auto_memory_dir`]'s, so the check and the
/// migration always look at the same directory.
/// What: [`IndexState::Empty`] when either the project dir or the config dir is
/// unknown (there is nothing to name), else the index's own state.
/// Test: `auto_memory_fails_when_the_index_still_holds_facts`,
/// `auto_memory_ok_when_off_and_the_index_is_empty`.
/// The `tm doctor --fix` invocation that applies this check's repair.
const REMEDY: &str = "tm doctor --fix --yes";
/// The migration `--fix` deliberately does NOT run.
///
/// Why: `--fix` writes one boolean key; moving facts between two stores is a
/// data migration with its own failure modes, and an operator must choose to run
/// it. Naming it in the remedy text is how the row stays actionable anyway.
/// What: the command line, quoted verbatim in the `Holding` verdicts.
/// Test: `auto_memory_fails_when_the_index_still_holds_facts`.
const MIGRATE: &str = "tm memory import-auto-memory";
/// Write `autoMemoryEnabled` into the project tier under `--fix` (#7685).
///
/// Why: the launch path already writes this key, but only on a launch. A project
/// whose settings file predates #7685, or that an operator edited, has no other
/// route back to the directive short of hand-editing JSON. The repair is purely
/// additive — one boolean key, every other key preserved — which is why it is in
/// the auto-repairable set at all.
///
/// It is TWO-WAY for the same reason the launch write is: a repair that could
/// only ever write `false` would deepen the exact failure the check now warns
/// about, disabling the fallback on a host whose trusty-memory is down.
/// What: derives the wanted value as `!memory_reachable` and reads the PROJECT
/// tier only (the tier tm owns; a higher-precedence `settings.local.json` is the
/// operator's and is never rewritten, the same refusal `output_style_legacy_ids`
/// makes). Produces no step when that tier already reads the wanted value.
/// Otherwise plans the write in [`RepairMode::DryRun`], and in
/// [`RepairMode::Apply`] performs it through [`merge_settings_key`]. A write that
/// fails yields [`StepStatus::Failed`] carrying the error — never an `Applied`
/// step, so a `--fix` run cannot report a repair it did not make.
///
/// It deliberately does NOT migrate the auto-memory store (#7685): that is
/// [`MIGRATE`], an operator-run data move with its own failure modes, and a
/// `--fix` that quietly rewrote two stores would be the wrong shape of repair.
/// Test: `auto_memory_repair_applies_when_absent`,
/// `auto_memory_repair_restores_the_fallback_when_memory_is_down`,
/// `auto_memory_repair_dry_run_writes_nothing`,
/// `auto_memory_repair_is_silent_when_already_false`,
/// `auto_memory_repair_reports_a_write_failure`,
/// `auto_memory_repair_never_migrates`.