← Commit history

0.5.4: MERGE code-server settings.json instead of overwriting it. The cat > ran every hook window and silently destroyed anything the user had written there, with no message and no backup.

John Lauer ·a78ba30a8c ·2mo ago ·parent d1dfa66
2 files changed +92−36
install.sh+91−34
@@ -108,42 +108,99 @@ fi #    because claudeCode.initialPermissionMode stays "auto", and Claude Code still #    shows its own one-time consent dialog before bypass takes effect. Nothing here #    pre-accepts that dialog.-log "writing code-server settings.json"+log "merging code-server settings.json (managed keys only)" install -d -m 0755 "$HOME/.local/share/code-server/User"-cat > "$HOME/.local/share/code-server/User/settings.json" <<'SETTINGS'-{-  "security.workspace.trust.enabled": false,-  "security.workspace.trust.untrustedFiles": "open",-  "workbench.startupEditor": "none",-  "workbench.activityBar.location": "default",-  "workbench.activityBar.iconClickBehavior": "toggle",-  "workbench.colorTheme": "Adom Studio Dark (Brighter Text)",-  "editor.fontSize": 15,-  "workbench.statusBar.visible": false,-  "workbench.navigationControl.enabled": false,-  "workbench.secondarySideBar.visible": false,-  "workbench.secondarySideBar.defaultVisibility": "hidden",-  "claudeCode.allowDangerouslySkipPermissions": true,-  "claudeCode.initialPermissionMode": "auto",-  "claudeCode.preferredLocation": "panel",-  "chat.agent.enabled": false,-  "chat.commandCenter.enabled": false,-  "chat.agentsControl.enabled": false,-  "chat.unifiedAgentsBar.enabled": false,-  "github.copilot.chat.enabled": false,-  "github.copilot.enable": { "*": false },-  "github.gitAuthentication": false,-  "git.autofetch": false,-  "scm.defaultViewMode": "tree",-  "security.trustedDomains": ["*"],-  "workbench.trustedDomains.promptInTrustedWorkspace": false,-  "remote.portsAttributes": { "8821": { "onAutoForward": "silent" } },-  "remote.otherPortsAttributes": { "onAutoForward": "silent" },-  "remote.autoForwardPortsSource": "hybrid",-  "extensions.autoUpdate": true,-  "extensions.autoCheckUpdates": true+# MERGE, NEVER OVERWRITE. This block used to `cat >` the whole file, so every+# hook window (every 30 minutes) silently destroyed anything the user had put in+# their own VS Code settings: keybinding tweaks, font choices, extension config,+# formatter rules. They are power users; they absolutely will have written other+# stuff, and it came back deleted with no message and no backup.+#+# Now: ah OWNS the keys below and re-asserts them, and EVERY OTHER KEY THE USER+# HAS IS PRESERVED UNTOUCHED. A one-time backup is taken the first time a merge+# would change anything, so a user who lost settings to the old behaviour still+# has whatever was there at the moment we started merging.+python3 - "$HOME/.local/share/code-server/User/settings.json" <<'PYSETTINGS' || true+import json, os, shutil, sys++path = sys.argv[1]++# The managed set: what ah needs true for the product to behave. Anything not in+# here belongs to the user and is never written, reordered or removed by us.+MANAGED = {+    "security.workspace.trust.enabled": False,+    "security.workspace.trust.untrustedFiles": "open",+    "workbench.startupEditor": "none",+    "workbench.activityBar.location": "default",+    "workbench.activityBar.iconClickBehavior": "toggle",+    "workbench.colorTheme": "Adom Studio Dark (Brighter Text)",+    "editor.fontSize": 15,+    "workbench.statusBar.visible": False,+    "workbench.navigationControl.enabled": False,+    "workbench.secondarySideBar.visible": False,+    "workbench.secondarySideBar.defaultVisibility": "hidden",+    # TRUE only makes the "Bypass permissions" row APPEAR in the Modes menu.+    # Adom users are power users and that choice is theirs (John 2026-08-25).+    # Auto stays the default via initialPermissionMode, and Claude Code still+    # shows its own one-time consent dialog before bypass takes effect.+    "claudeCode.allowDangerouslySkipPermissions": True,+    "claudeCode.initialPermissionMode": "auto",+    "claudeCode.preferredLocation": "panel",+    "chat.agent.enabled": False,+    "chat.commandCenter.enabled": False,+    "chat.agentsControl.enabled": False,+    "chat.unifiedAgentsBar.enabled": False,+    "github.copilot.chat.enabled": False,+    "github.copilot.enable": {"*": False},+    "github.gitAuthentication": False,+    "git.autofetch": False,+    "scm.defaultViewMode": "tree",+    "security.trustedDomains": ["*"],+    "workbench.trustedDomains.promptInTrustedWorkspace": False,+    "remote.portsAttributes": {"8821": {"onAutoForward": "silent"}},+    "remote.otherPortsAttributes": {"onAutoForward": "silent"},+    "remote.autoForwardPortsSource": "hybrid",+    "extensions.autoUpdate": True,+    "extensions.autoCheckUpdates": True, }-SETTINGS++existing, unreadable = {}, False+if os.path.exists(path):+    try:+        with open(path) as f:+            existing = json.load(f)+        if not isinstance(existing, dict):+            existing, unreadable = {}, True+    except Exception:+        # A settings.json we cannot parse is NOT a settings.json we may replace:+        # it is far more likely a user mid-edit than a corrupt file, and the old+        # behaviour would have thrown their work away. Back it up and start from+        # the managed set rather than merging into something we cannot read.+        unreadable = True++if unreadable and os.path.exists(path):+    shutil.copy2(path, path + ".unreadable.bak")+    print("  settings.json was not readable JSON: kept a copy at settings.json.unreadable.bak")+    existing = {}++merged = dict(existing)+merged.update(MANAGED)++if merged != existing:+    # One-time safety net for anyone who still has settings from before the merge.+    if os.path.exists(path) and not os.path.exists(path + ".pre-merge.bak"):+        shutil.copy2(path, path + ".pre-merge.bak")+    tmp = path + ".tmp"+    with open(tmp, "w") as f:+        json.dump(merged, f, indent=2)+        f.write("\n")+    os.replace(tmp, path)          # atomic: never a half-written settings file+    kept = [k for k in existing if k not in MANAGED]+    print("  merged %d managed key(s); preserved %d of the user's own: %s"+          % (len(MANAGED), len(kept), ", ".join(sorted(kept)[:6]) or "none"))+else:+    print("  settings.json already correct, nothing written")+PYSETTINGS  # 5. code-server config.yaml — silence code-server's own update nags. install -d -m 0755 "$HOME/.config/code-server"
package.json+1−2
@@ -4,10 +4,9 @@   "type": "bootstrap",   "title": "Adom Hydrogen (platform base)",   "brief": "Layer Hydrogen's workspace onto a standard Adom workstation.",-  "version": "0.5.3",+  "version": "0.5.4",   "description": "Hydrogen's PLATFORM-GENERIC workspace layer on top of adom/core. Installs adom/core for the full Adom ecosystem, then adds everything common to running inside Hydrogen on ANY platform: the runtime-neutral hydrogen-* skills and the generic editor configuration (Claude Code CLI + extension, VS Code settings, trusted domains). Platform-specific layers depend on THIS: adom/hydrogen-windows-bootstrap (WSL2), adom/hydrogen-mac-bootstrap, adom/hydrogen-ubuntu-bootstrap. The reference design other orgs copy to build their own bootstrap on adom/core.",   "org": "adom",-  "platform": "any",   "tags": [     "bootstrap",     "hydrogen",