From 1ad11da1b354e5ea95d818cb67ca6142b0e19c15 Mon Sep 17 00:00:00 2001 From: Shun Kakinoki Date: Mon, 27 Jul 2026 02:12:45 +0900 Subject: [PATCH] fix(codex): persist desktop settings in atom state --- config/codex/default.nix | 8 +++---- config/codex/sync-desktop-settings.sh | 29 ++++++++++++----------- spec/activate_config_spec.sh | 33 +++++++++++++++------------ 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/config/codex/default.nix b/config/codex/default.nix index 477a97caa..a9820db1e 100644 --- a/config/codex/default.nix +++ b/config/codex/default.nix @@ -22,10 +22,10 @@ in "${syncDesktopSettings}" ''; - # Codex caches Desktop preferences in memory and replaces its complete state - # file after activation. Restore managed top-level global-state keys after - # each app-owned rewrite; the synchronizer exits without writing once the - # values already match. + # Codex caches Desktop preferences in its persisted atom state and replaces + # the complete global-state file after activation. Restore managed atom-state + # keys after each app-owned rewrite; the synchronizer exits without writing + # once the values already match. launchd.agents.codex-desktop-settings-sync = lib.mkIf pkgs.stdenv.isDarwin { enable = true; config = { diff --git a/config/codex/sync-desktop-settings.sh b/config/codex/sync-desktop-settings.sh index 73bfc0a87..9595e2d51 100755 --- a/config/codex/sync-desktop-settings.sh +++ b/config/codex/sync-desktop-settings.sh @@ -9,9 +9,9 @@ GLOBAL_STATE="$HOME/.codex/.codex-global-state.json" mkdir -p "$HOME/.codex" -# Codex stores these preferences as top-level global-state keys. Older Desktop -# builds kept them inside electron-persisted-atom-state, so also require those -# legacy copies to be absent before treating the file as synchronized. +# Codex Desktop stores these preferences in its persisted atom-state object. +# Top-level copies are ignored by the settings UI, so require those stale copies +# to be absent before treating the file as synchronized. # # Codex keeps this state in memory and periodically replaces the entire file. # Avoid a write when the managed values already match so launchd's file watch @@ -19,11 +19,11 @@ mkdir -p "$HOME/.codex" if [[ -s $GLOBAL_STATE ]] && "$JQ_BIN" -e --slurpfile settings "$DESKTOP_SETTINGS_JSON" ' $settings[0] as $managed | . as $state - | ($managed | to_entries | all(. as $entry | - $state[$entry.key] == $entry.value)) + | (($state["electron-persisted-atom-state"] // {}) as $atoms + | ($managed | to_entries | all(. as $entry | + $atoms[$entry.key] == $entry.value))) and - ((.["electron-persisted-atom-state"] // {}) as $legacy - | ($managed | keys | all(. as $key | $legacy | has($key) | not))) + ($managed | keys | all(. as $key | $state | has($key) | not)) ' "$GLOBAL_STATE" >/dev/null; then exit 0 fi @@ -34,17 +34,16 @@ trap 'rm -f "$GLOBAL_STATE_TMP"' EXIT if [[ -s $GLOBAL_STATE ]]; then "$JQ_BIN" --slurpfile settings "$DESKTOP_SETTINGS_JSON" ' $settings[0] as $managed - | . + $managed - | if (.["electron-persisted-atom-state"] | type) == "object" then - .["electron-persisted-atom-state"] |= - with_entries(select(.key as $key | $managed | has($key) | not)) - else - . - end + | (if (.["electron-persisted-atom-state"] | type) == "object" + then .["electron-persisted-atom-state"] + else {} + end) as $atoms + | with_entries(select(.key as $key | $managed | has($key) | not)) + | .["electron-persisted-atom-state"] = ($atoms + $managed) ' "$GLOBAL_STATE" >"$GLOBAL_STATE_TMP" else "$JQ_BIN" -n --slurpfile settings "$DESKTOP_SETTINGS_JSON" ' - $settings[0] + {"electron-persisted-atom-state": $settings[0]} ' >"$GLOBAL_STATE_TMP" fi diff --git a/spec/activate_config_spec.sh b/spec/activate_config_spec.sh index 6e49813a0..104c26fc8 100644 --- a/spec/activate_config_spec.sh +++ b/spec/activate_config_spec.sh @@ -39,7 +39,7 @@ When run grep -qF 'reviewDelivery = "inline"' "$CONFIG_TOML" The status should be success End -It 'merges managed top-level Desktop settings without replacing unrelated state' +It 'merges managed Desktop settings into the app-owned atom state without replacing unrelated state' TMP_HOME="$(mktemp -d)" mkdir -p "$TMP_HOME/.codex" cat >"$TMP_HOME/.codex/.codex-global-state.json" <<'JSON' @@ -52,16 +52,17 @@ cat >"$TMP_HOME/.codex/.codex-global-state.json" <<'JSON' } JSON -When run bash -c 'HOME="$1" bash "$2" "$3" "$4" "$5" "$6" "$7" && jq -r ".[\"unrelated-top-level\"], .[\"electron-persisted-atom-state\"][\"unrelated-setting\"], .[\"git-always-force-push\"], .[\"git-pull-request-merge-method\"], .[\"worktree-keep-count\"]" "$1/.codex/.codex-global-state.json"' _ "$TMP_HOME" "$SCRIPT" "$CONFIG_TOML" "$HOOKS_JSON" "$DESKTOP_SETTINGS_JSON" "$(command -v jq)" "$SYNC_SCRIPT" +When run bash -c 'HOME="$1" bash "$2" "$3" "$4" "$5" "$6" "$7" && jq -r ".[\"unrelated-top-level\"], .[\"electron-persisted-atom-state\"][\"unrelated-setting\"], .[\"electron-persisted-atom-state\"][\"git-always-force-push\"], .[\"electron-persisted-atom-state\"][\"git-pull-request-merge-method\"], .[\"electron-persisted-atom-state\"][\"worktree-keep-count\"], has(\"worktree-keep-count\")" "$1/.codex/.codex-global-state.json"' _ "$TMP_HOME" "$SCRIPT" "$CONFIG_TOML" "$HOOKS_JSON" "$DESKTOP_SETTINGS_JSON" "$(command -v jq)" "$SYNC_SCRIPT" The status should be success The line 1 should eq 'preserved' The line 2 should eq '42' The line 3 should eq 'true' The line 4 should eq 'squash' The line 5 should eq '300' +The line 6 should eq 'false' End -It 'restores managed top-level Desktop settings after the app replaces its state' +It 'restores managed atom-state Desktop settings after the app replaces its state' TMP_HOME="$(mktemp -d)" mkdir -p "$TMP_HOME/.codex" cat >"$TMP_HOME/.codex/.codex-global-state.json" <<'JSON' @@ -73,40 +74,44 @@ cat >"$TMP_HOME/.codex/.codex-global-state.json" <<'JSON' } JSON -When run bash -c 'HOME="$1" bash "$2" "$3" "$4" && jq -r ".[\"unrelated-top-level\"], .[\"electron-persisted-atom-state\"][\"unrelated-setting\"], .[\"git-branch-prefix\"], .[\"worktree-keep-count\"]" "$1/.codex/.codex-global-state.json"' _ "$TMP_HOME" "$SYNC_SCRIPT" "$DESKTOP_SETTINGS_JSON" "$(command -v jq)" +When run bash -c 'HOME="$1" bash "$2" "$3" "$4" && jq -r ".[\"unrelated-top-level\"], .[\"electron-persisted-atom-state\"][\"unrelated-setting\"], .[\"electron-persisted-atom-state\"][\"git-branch-prefix\"], .[\"electron-persisted-atom-state\"][\"worktree-keep-count\"], has(\"worktree-keep-count\")" "$1/.codex/.codex-global-state.json"' _ "$TMP_HOME" "$SYNC_SCRIPT" "$DESKTOP_SETTINGS_JSON" "$(command -v jq)" The status should be success The line 1 should eq 'preserved' The line 2 should eq '42' The line 3 should eq 'codex/' The line 4 should eq '300' +The line 5 should eq 'false' End -It 'removes legacy nested copies of managed Desktop settings' +It 'removes stale top-level copies of managed Desktop settings' TMP_HOME="$(mktemp -d)" mkdir -p "$TMP_HOME/.codex" cat >"$TMP_HOME/.codex/.codex-global-state.json" <<'JSON' { + "git-branch-prefix": "stale/", + "worktree-keep-count": 15, "electron-persisted-atom-state": { - "unrelated-setting": 42, - "git-branch-prefix": "legacy/", - "worktree-keep-count": 15 + "unrelated-setting": 42 } } JSON -When run bash -c 'HOME="$1" bash "$2" "$3" "$4" && jq -r ".[\"electron-persisted-atom-state\"][\"unrelated-setting\"], (.[\"electron-persisted-atom-state\"] | has(\"git-branch-prefix\")), (.[\"electron-persisted-atom-state\"] | has(\"worktree-keep-count\")), .[\"git-branch-prefix\"], .[\"worktree-keep-count\"]" "$1/.codex/.codex-global-state.json"' _ "$TMP_HOME" "$SYNC_SCRIPT" "$DESKTOP_SETTINGS_JSON" "$(command -v jq)" +When run bash -c 'HOME="$1" bash "$2" "$3" "$4" && jq -r ".[\"electron-persisted-atom-state\"][\"unrelated-setting\"], .[\"electron-persisted-atom-state\"][\"git-branch-prefix\"], .[\"electron-persisted-atom-state\"][\"worktree-keep-count\"], has(\"git-branch-prefix\"), has(\"worktree-keep-count\")" "$1/.codex/.codex-global-state.json"' _ "$TMP_HOME" "$SYNC_SCRIPT" "$DESKTOP_SETTINGS_JSON" "$(command -v jq)" The status should be success The line 1 should eq '42' -The line 2 should eq 'false' -The line 3 should eq 'false' -The line 4 should eq 'codex/' -The line 5 should eq '300' +The line 2 should eq 'codex/' +The line 3 should eq '300' +The line 4 should eq 'false' +The line 5 should eq 'false' End It 'does not rewrite state when managed Desktop settings already match' TMP_HOME="$(mktemp -d)" mkdir -p "$TMP_HOME/.codex" -cp -f "$DESKTOP_SETTINGS_JSON" "$TMP_HOME/.codex/.codex-global-state.json" +jq -n --slurpfile settings "$DESKTOP_SETTINGS_JSON" '{ + "unrelated-top-level": "preserved", + "electron-persisted-atom-state": $settings[0] +}' >"$TMP_HOME/.codex/.codex-global-state.json" When run bash -c 'HOME="$1" bash "$2" "$3" "$4" && before=$(ls -di "$1/.codex/.codex-global-state.json") && before=${before%% *} && HOME="$1" bash "$2" "$3" "$4" && after=$(ls -di "$1/.codex/.codex-global-state.json") && after=${after%% *} && test "$before" = "$after" && printf "%s\\n" unchanged' _ "$TMP_HOME" "$SYNC_SCRIPT" "$DESKTOP_SETTINGS_JSON" "$(command -v jq)" The status should be success