fix(plugins): keep a plugin's tokens and local files across store updates (#755)

* fix(plugins): keep a plugin's tokens and local files across store updates

A monorepo plugin update replaces the plugin directory with the fresh
download and deletes the old copy, taking with it everything the plugin
wrote beside itself. On 2026-10-04 updating calendar 1.2.9 -> 1.2.12 deleted
token.pickle and credentials.json, and the calendar stopped until they were
restored from a backup.

Before the set-aside copy is discarded (store update, reinstall over an
existing copy, install_from_url replace), carry over files the plugin's
.gitignore excludes plus known secret/state files (*.pickle, token.json,
credentials.json, config_secrets.json, .pkce_code_verifier). Files the new
release ships win; byte code and .git are not carried; if a copy fails the
old copy is kept.

The git-pull path no longer sweeps untracked tokens into its auto-stash,
which is never popped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(plugins): find the new copy via _existing_install, as install_plugin does

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(on-demand): find the write under test by key, not by position

TestARestoreWithNothingToResume took the last cache_manager.set call to be
the on-demand state, but the controller's font-usage publisher thread writes
font_usage_snapshot to the same mock, and on a slow runner it lands last.
Failing on main since #748 (Python 3.11 job). Same fix for the named-mode
restart test, which had the same race.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(starlark): fake only the editor launch, not every Popen in the request

TestPixletEditorHostDefaultsButDoesNotOverride patched subprocess.Popen for
the whole request. When the captive-portal before_request hook's 30s AP-mode
cache had expired, its `systemctl is-active hostapd` check went through
subprocess.run, got the fake process, and raised TypeError (run() uses the
process as a context manager): a 500 instead of 200. Seen on the Python 3.13
job; reproduced locally by forcing the cache to expire. Other calls now reach
the real Popen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-10-05 09:06:56 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 3f920f2899
commit bb475a79ea
5 changed files with 516 additions and 9 deletions
+11
View File
@@ -1590,6 +1590,17 @@ read any of them:
### Fixes
- Updating a plugin from the store no longer deletes the files it wrote
beside itself. A monorepo update replaces the plugin directory with the
fresh download and deletes the old copy, so calendar's Google OAuth files
(`token.pickle`, `credentials.json`) were lost on every update and the
calendar stopped until they were restored by hand. Before the old copy is
removed, the update now copies over anything the plugin's `.gitignore`
excludes plus known secret/state files (`*.pickle`, `token.json`,
`credentials.json`, `config_secrets.json`, `.pkce_code_verifier`); files the
new release ships are never overwritten, and byte code is not carried. A
plugin updated with `git pull` no longer sweeps an untracked token into the
auto-stash, which is never popped (`src/plugin_system/plugin_local_files.py`).
- Quieter routine logging. Every rotation logged each mode twice
("Switching to mode", then "Processing mode"), and a mode with nothing to
show added "display() returned False" and "No content to display". Those