danielmiessler / danielmiessler/LifeOS

ConfigSystem.md documents settings.json as generated and never-edit while the shipped install deliberately never establishes the split; hand-creating settings.system.json silently flips which file is authoritative

Open Beginner friendly
#2,086 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
19k
Forks
2.5k
Avg merge
8d 17h
Merged PRs (30d)
1

Description

## Version

LifeOS 7.40.4 / Config

## What is broken

`ConfigSystem.md:39` describes `settings.json` as `SYSTEM (generated)` — *"Merged at SessionStart by
`MergeSettings.ts` from `settings.system.json` + `settings.user.json`. Read-only at runtime —
manual edits get overwritten next session."* `:70` makes it an instruction: ***"NEVER edit
`settings.json` directly** — it's generated. Edits get overwritten on next SessionStart."* Neither
sentence is conditional, and `:66-67` sends the operator to `settings.user.json` and
`settings.system.json` for the two classes of change.

On a stock install neither of those two source files exists, and by design: `InstallSettings.ts`
writes the payload template straight to `/settings.json`, and `MergeSettings.ts:601-607`
no-ops on the missing inputs under a comment naming the behaviour a deliberate fresh-install guard.
So on that install the documentation is false in both directions at once:

- the two files `:66-67` tells the operator to edit do not exist;
- `settings.json` is not regenerated on any session, so *"manual edits get overwritten next
session"* is wrong — a hand edit to `settings.json` is in fact the only edit that takes effect,
and it survives indefinitely;
- the one file `:70` forbids editing is therefore the only place a change can be made.

The second half is the sharper one, and it is undocumented in either state. An operator who follows
`:66-67` and hand-creates `settings.system.json` **silently changes which file is authoritative**.
The merge starts firing that session, `settings.json` becomes genuinely generated, and any edits
they had made to it — the edits the install left them no alternative to — are overwritten with no
warning. Nothing errors, both messages are calm, and both exits are 0. The negative control below
demonstrates that flip on the unpatched tag.

Reader and writer:

- contract — `LifeOS/install/LIFEOS/DOCUMENTATION/Config/ConfigSystem.md:39` — "Merged at SessionStart by `MergeSettings.ts` … Read-only at runtime — manual edits get overwritten next session"
- contract — `LifeOS/install/LIFEOS/DOCUMENTATION/Config/ConfigSystem.md:66-67` — "edit `LIFEOS/USER/CONFIG/settings.user.json`" / "edit `settings.system.json`"
- contract — `LifeOS/install/LIFEOS/DOCUMENTATION/Config/ConfigSystem.md:70` — "**NEVER edit `settings.json` directly** — it's generated."
- writer — `LifeOS/Tools/InstallSettings.ts:71-72` — template `install/settings.system.json` → target `/settings.json`; no `settings.system.json` is written at the config root
- reader — `LifeOS/install/hooks/hooks.json:456` — SessionStart runs `SettingsBackport.ts` then `MergeSettings.ts --system … --user … --output …`
- the deliberate no-op — `LifeOS/install/LIFEOS/TOOLS/MergeSettings.ts:601-607` — "Fresh-install guard (audit 20260702 F-001)"

## Where (file:line)

`LifeOS/install/LIFEOS/DOCUMENTATION/Config/ConfigSystem.md:39`

## Repro on a clean tree

```shell
git clone --branch v7.40.4 --depth 1 https://github.com/danielmiessler/LifeOS.git /tmp/lifeos-7404
cd /tmp/lifeos-7404 && git rev-parse HEAD
# → be9e8ef889f00a29f4fd677dee4772fdf32e07ce

mkdir -p /tmp/fresh/.claude /tmp/fresh/.config/LIFEOS

# Deploy the runtime, then the settings, exactly as setup does.
env HOME=/tmp/fresh CLAUDE_CONFIG_DIR=/tmp/fresh/.claude \
bun LifeOS/Tools/DeployCore.ts --config-root /tmp/fresh/.claude --skill-root ./LifeOS --apply >/dev/null
env HOME=/tmp/fresh CLAUDE_CONFIG_DIR=/tmp/fresh/.claude \
bun LifeOS/Tools/InstallSettings.ts --config-root /tmp/fresh/.claude --skill-root ./LifeOS --apply
# → { "ok": true, "apply": true, "target": "/tmp/fresh/.claude/settings.json",
# → "envValuesExpanded": 3, "mode": "create", "topLevelKeys": 28 }

# Neither file ConfigSystem.md:66-67 tells the operator to edit was created.
ls -1 /tmp/fresh/.claude | grep -i settings
echo "settings.system.json present? $([ -f /tmp/fresh/.claude/settings.system.json ] && echo YES || echo NO)"
echo "USER/CONFIG/settings.user.json present? $([ -f /tmp/fresh/.claude/LIFEOS/USER/CONFIG/settings.user.json ] && echo YES || echo NO)"
# → settings.json
# → settings.system.json present? NO
# → USER/CONFIG/settings.user.json present? NO

# Now the SessionStart command, taken verbatim from install/hooks/hooks.json:456.
before=$(shasum -a 256 /tmp/fresh/.claude/settings.json | cut -d' ' -f1)
env HOME=/tmp/fresh bash -c 'bun $HOME/.claude/LIFEOS/TOOLS/SettingsBackport.ts; bun $HOME/.claude/LIFEOS/TOOLS/MergeSettings.ts --system $HOME/.claude/settings.system.json --user $HOME/.claude/LIFEOS/USER/CONFIG/settings.user.json --output $HOME/.claude/settings.json'
after=$(shasum -a 256 /tmp/fresh/.claude/settings.json | cut -d' ' -f1)
echo "changed? $([ "$before" = "$after" ] && echo NO || echo YES)"
# → settings-layer not present (no settings.system.json / settings.user.json) — nothing to backport
# → MergeSettings: input(s) absent (system/user settings layer not established) — nothing to merge
# → changed? NO

# Byte-identical. Repeat the session as many times as you like; the answer does not
# change, so "manual edits get overwritten next session" never becomes true.
```

## Negative control

The claim is about the docs and the transition, not about `MergeSettings` being broken, so the
control has to show the machinery working. Establish the split by hand — which is exactly what
`ConfigSystem.md:66-67` tells the operator to do — and run the identical SessionStart command:

```shell
cp /tmp/fresh/.claude/settings.json /tmp/fresh/.claude/settings.system.json
mkdir -p /tmp/fresh/.claude/LIFEOS/USER/CONFIG
printf '{"env":{"PROBE_MARKER":"control-ran"}}\n' > /tmp/fresh/.claude/LIFEOS/USER/CONFIG/settings.user.json
before=$(shasum -a 256 /tmp/fresh/.claude/settings.json | cut -d' ' -f1)
env HOME=/tmp/fresh bash -c 'bun $HOME/.claude/LIFEOS/TOOLS/SettingsBackport.ts; bun $HOME/.claude/LIFEOS/TOOLS/MergeSettings.ts --system $HOME/.claude/settings.system.json --user $HOME/.claude/LIFEOS/USER/CONFIG/settings.user.json --output $HOME/.claude/settings.json'
after=$(shasum -a 256 /tmp/fresh/.claude/settings.json | cut -d' ' -f1)
echo "changed? $([ "$before" = "$after" ] && echo NO || echo YES)"
grep -c PROBE_MARKER /tmp/fresh/.claude/settings.json
```

```
no merge snapshot at /tmp/fresh/.claude/LIFEOS/MEMORY/STATE/settings-merge-snapshot.json — skipping backport (next MergeSettings run writes it)
Merged 28 system keys + 1 user overlays into /tmp/fresh/.claude/settings.json
changed? YES
1
```

Red twice over. First, everything `ConfigSystem.md` describes works — it is only ever reached on an
install where somebody established the split by hand, which the installer deliberately does not do.
Second, and this is the undocumented part: one `cp` moved authority from `settings.json` to
`settings.system.json`, `settings.json` was rewritten in the same session, and no message said so.
On a real install the file overwritten there is the one holding the operator's own edits.

## Suggested fix

Shape only, **untested**. This is a docs change in the first instance, since the install behaviour
is settled:

1. **State the condition.** Make `:39` and `:70` conditional on the split existing. A stock install
has one file, it is not generated, and editing it is the supported path — say that, rather than
forbidding the only editable file.
2. **Document the transition.** Add the step that establishes the split, and say plainly that
taking it makes `settings.json` generated from that session on and overwrites what is in it.
That is the sentence that does not exist in either state today.
3. Optionally, have `MergeSettings`'s fresh-install message name the transition — it already
detects the exact condition, and it is the only line an operator sees.

The alternative — having `InstallSettings.ts` establish the split so the docs become true as
written — is PR #1432's direction and was closed unmerged, so I am not re-proposing it.

## Before submitting

- [x] I searched open and closed issues for this defect.
Searched `settings.system.json MergeSettings`, `settings split merge fresh install`. Prior
art exists and this is scoped around it: **#1402** (closed COMPLETED) states in its body that
Setup overlays the payload as `settings.json` *"so nothing ever lands at
`~/.claude/settings.system.json`"*; **PR #1432** proposed writing a standing
`settings.system.json` on hooks install and was **closed unmerged**; **#1575** (closed
COMPLETED) covers the `MergeSettings` user-overlay path footgun; **#1860** (closed COMPLETED)
covers `settings.system.json` hardening never reaching updated installs. All four concern the
install and merge behaviour. None of them changes `ConfigSystem.md`, and none documents the
authority flip. This report is the docs-side contradiction those four left open.
- [x] The repro runs against a clean tree of the version above, not against my modified install.
Fresh `--depth 1` clone; `DeployCore` and `InstallSettings` run with `--config-root` and
`HOME` pinned to a scratch directory, so nothing resolves to a real install.
- [x] I removed personal data from the pasted output — real names, absolute home paths, tokens, my
own content. Paths are `/tmp/...`; `PROBE_MARKER` is synthetic.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with ConfigSystem.md:39,66-70, then compare its claims with InstallSettings.ts:71-72, hooks.json:456, and MergeSettings.ts:601-607. Run the clean-tree reproduction to confirm the stock install and hand-established split differ. Done means the documentation distinguishes both states and clearly explains the authority change and its overwrite risk.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
documentation
Issue type
Documentation
Difficulty
2/5
Estimated time
Half a day
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
82/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.