Skip to content

Install newer Python support without asking, now that stored data survives it - #187

Open
srbaker wants to merge 1 commit into
release-1.10.0from
fix/186-python-update-prompt
Open

srbaker wants to merge 1 commit into
release-1.10.0from
fix/186-python-update-prompt

Conversation

@srbaker

@srbaker srbaker commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator

Closes #186.

Lands in #185 (release-1.10.0), so it ships in 1.10.0.

What changes for the user

  • An update to Python support installs without a prompt. The next time the database starts or you run Python, GemDB installs it. This applies to every trigger, auto-start and MCP agents included, as the MCP server's own update already does.
  • gemdb.reinstallPythonOnUpdate is used again. It defaults to on. Off, the database keeps its current Python support until you run Reinstall the Python Execution Engine (the status view's Python row offers it).
  • Reinstall the Python Execution Engine no longer asks. Running the command is the request.
  • A first install, and the reinstall after an engine upgrade, are unchanged.

Why the prompt goes, not just its wording

git log -S confirmGrailInstall leads to 05f720f, which added the prompt in 1.7.0 for one reason. Filing a newer Grail into a database with Python support broke stored data. install.gs recreated every runtime class, and committed objects kept the old ones. That was measured on 1.6.0 → 1.7.0: stored datetimes, Decimals, tuples, sets, None and user-class instances failed isinstance and comparisons. The prompt was a data-loss warning, and nothing else in it called for a question.

That reason is gone:

  • Grail upgrades in place. It installs with Rowan, which keeps its classes. install-grail.sh then runs redeployModules.gs, which rebuilds every deployed module into its committed instance and classes (Grail #1791; Grail's Persistent_Modules_and_Classes.md D7 and Releases_and_Rowan.md).
  • It is measured. Grail's upgrade test writes an app's data under one Grail and installs another over it. It then checks that every stored object is still an instance of the current class and runs its code. The stored data covers dates, datetimes, timedeltas, Decimals, Fractions, tuples, sets, None, enum members, namedtuples, exceptions and user classes. The grail-upgrade.yml nightly runs it from release=c8cbc92, which is 1.9.0's pin and so the Grail every 1.9.0 database has. It passed on 2026-10-28 and 2026-10-29. #1867 measured that case at 243/0.
  • 1.10.0 already does it unasked. The engine upgrade reinstalls Grail without a second question, which contradicted the warning.

A reworded question would have nothing left to ask. The remaining cases (below) are not helped by a question before the install. So the honest behaviour is the pre-1.7 one.

Why it is not under "Asked" in automation-boundary.md: the install is persistent, but so is the MCP server's install, which was already automated. Both are GemDB's own code in GemDB's own database, and the next engine upgrade reinstalls Grail regardless. The setting gives anyone who wants control a way to opt out.

The setting's migration story

gemdb.reinstallPythonOnUpdate was never removed. 05f720f kept it in the manifest, deprecated, so that existing values were not reported as unknown. Un-deprecating it restores its pre-1.7 meaning:

  • People who never set it get the default, on.
  • Anyone who set it to false before 1.7.0 gets what they asked for then: no automatic update.

The .gemdb-grail-declined record that 1.7.0–1.9.0 wrote is ignored. The next successful install removes it, as before. It is per-build anyway, so it could not match 1.10.0's build.

What is still not carried over, and where it is said

The new wording claims only what is measured. These limits come from Grail's docs at the pinned commit:

  • A class whose bases the update changed, while instances of it (or of a class below it) are stored. The redeploy rebuilds it when nothing is stored against it. Otherwise the module stays stale, the GemDB log names it, and importing it raises with the advice to run gemdb.schema.rebase(...).
  • A class statement inside a function other than namedtuple's. It makes a new class on every rebuild, so instances stored before keep the old class.

Both are in the README paragraph, the CHANGELOG entry and automation-boundary.md.

A deliberate non-change

resumeRunning still files in only a first install, not an update. After an extension update every window activates at once onto the running database, and nothing locks a Grail install across windows. So the update waits for the database's next use (a cell, Start, the Shell, an agent call) rather than racing installs from editors that only opened. Pre-1.7 GemDB did install from activation. I left that out on purpose rather than bringing the race back in a release PR.

Files

  • src/lifecycle.ts: grailFileInAgreed and confirmGrailInstall are replaced by grailFileInDue, which reads the setting. reinstallGrail no longer asks. ASKS_ABOUT_PYTHON_UPDATES is renamed ASKS_ABOUT_ENGINE_UPGRADES, its only remaining use. Comments are updated.
  • src/config.ts: reinstallPythonOnUpdate() is restored.
  • src/grail.ts, src/paths.ts: the declined-record writer and reader are removed. The cleanup of a leftover record is kept and commented.
  • src/statusView.ts: the Python row's "update available" tooltip no longer says data stops working. It says when the update installs, or that the setting is holding it back.
  • package.json: the setting's description is rewritten and deprecationMessage is removed.
  • README.md: the "How setup runs" paragraph is rewritten, and the setting is added to the settings table and its intro.
  • docs/automation-boundary.md: the entry moves from Asked to Automated, keeping the 1.6.0 measurement as history. The engine-upgrade entry's reference is updated.
  • CHANGELOG.md: new 1.10.0 entry, "Newer Python support installs without asking again".
  • Tests: databaseStarted.test.ts (the update block is rewritten), statusRows.test.ts, manifest.test.ts, engineUpgradeStart.test.ts (stale mocks removed).

Testing

I wrote the tests first and watched them fail (7 in databaseStarted/statusRows, 1 in manifest), then made the change. The new tests cover:

  • an update installs without a prompt and reports filedGrail: update;
  • it installs for Start, Shell, auto-start and MCP;
  • with the setting off it does not install, and still starts;
  • a first install ignores the setting;
  • Reinstall does not ask, with the setting on or off;
  • the status row's tooltip in both states;
  • the manifest's setting is on by default and not deprecated.

Results:

  • npm test: 71 files, 1318 tests passed.
  • lint, lint:toolchain, format:check, typecheck and typecheck:strict are clean.
  • npm run release:notes 1.10.0 renders the new entry.
  • Not run: the integration suite and an end-to-end update over a 1.9.0 database. Both need the pinned engine under ~/GemDB, which I left alone. The evidence for "stored data survives" is Grail's upgrade test, cited above.

🤖 Generated with Claude Code

…vives it

1.7.0 began asking before filing a newer Grail into a database that already
had one, because install.gs recreated every runtime class and committed
objects kept the old ones: stored dates, Decimals, tuples, sets, None and
user-class instances stopped behaving as their types. That is no longer
true. Grail installs with Rowan, keeps its classes, and install-grail.sh
rebuilds every deployed module into its committed instance and classes
(Grail #1791). Grail's upgrade test checks every stored object against the
current class nightly, from 1.9.0's pin c8cbc92 (Grail #1867). The 1.10.0
engine upgrade already reinstalls Grail without a second question.

So an update is installed like the MCP server's, from every trigger, and
gemdb.reinstallPythonOnUpdate governs it again, default on, as it did
before 1.7.0: it was kept in the manifest, deprecated, so a value set then
means what it meant. Off, the update waits for Reinstall the Python
Execution Engine, which no longer asks either: the command is the request.
resumeRunning still files in only a first install, so windows activating
together after an update do not race installs.

The prompt, the status row's tooltip, the setting's description, the
README and docs/automation-boundary.md now say what is measured, and the
two cases that still need the user: a class whose bases the update changed
while instances are stored (its import is refused with the
gemdb.schema.rebase advice), and a class statement inside a function,
which is new on every rebuild.

Closes #186.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@srbaker srbaker self-assigned this Oct 29, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant