New learning: docs/solutions/tooling-decisions/upgrade-local-grav-core-rebuild-docker-image.md — the local Grav core is baked into the Docker image (only ./user is bind-mounted), so it upgrades by a Dockerfile URL bump + image rebuild + `docker rm -f` recreate, not the `gpm self-upgrade` the servers use (non-durable in-container). Refreshed three docs this exposed as stale/incomplete: - local-setup.md: rewrote the stale "newer Grav RC" section with the durable rebuild procedure (recreate gotcha, verify, plugin refresh, non-durability note). - deploy-cycle.md: Phase 0 now upgrades the local core; state-model notes the image as a fourth surface beyond the three server layers. - stale-grav-version-blocks-api-plugin-install.md: version-authority surfaces 3 -> 4 (hardcoded Dockerfile URL); clarified .env* GRAV_VERSION governs fresh remote installs only, never the local Docker core. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
9.4 KiB
title, date, category, module, problem_type, component, severity, symptoms, root_cause, resolution_type, related_components, tags
| title | date | category | module | problem_type | component | severity | symptoms | root_cause | resolution_type | related_components | tags | |||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Admin2 login fails silently because a stale GRAV_VERSION installs an rc core below the api plugin's version floor | 2026-07-04 | docs/solutions/integration-issues | grav / production deploy / plugin install | integration_issue | authentication | high |
|
config_error | environment_setup |
|
|
Admin2 login fails silently because a stale GRAV_VERSION installs an rc core below the api plugin's version floor
Problem
On a fresh Grav production install, Admin2 login fails silently because the api plugin — which Admin2 authenticates through — never installed. GPM refused to serve it: a stale GRAV_VERSION in .env.prod had installed Grav 2.0.0-rc.10, and the api plugin requires Grav core >=2.0.4. GPM filters offered packages by the installed core version, so on an rc.10 core the api plugin was excluded from results entirely and reported as "not found." Admin2 was present (and depends on api), but its login POST hit an /api/... route that was never registered, so authentication silently 404'd before it ever reached Grav's auth layer.
Symptoms
- Admin login at
/adminsilently fails: the login button disables briefly, re-enables, and shows no error. Nothing appears inlogs/grav.log— a wrong password would log a failed-attempt warning, so its absence means auth was never reached. - The Admin2 SPA's background login request (to an
/api/...endpoint) returns HTTP 404 withcontent-type: application/json. GET /api/v1/pagesreturns 404 on prod, but 401 Unauthorized on the working local install — i.e. the api route isn't registered on prod at all.ls user/plugins/apion the server: No such file or directory — the plugin was never installed, even thoughadmin2(which depends on it) was.php bin/gpm install ... api -y→"These packages were not found on Grav: api", even afterphp bin/gpm index -f.
What Didn't Work
- Committing/deploying the api plugin config (
enabled/route/session_enabled, moved from the untrackeduser/plugins/api/api.yamlinto the trackeduser/config/plugins/api.yaml). This was a real, necessary fix for a different latent problem, but it did not fix login: you cannot configure a plugin that isn't installed. Still 404. - Forcing a GPM index refresh (
php bin/gpm index -f). No effect. "Package not found" here is not a stale-index problem — GPM filters the packages it offers by the installed Grav core version, and rc.10 is below the api plugin's>=2.0.4requirement, soapiis excluded from results entirely. - Assuming "same channel = same availability." Local (Grav 2.0.4,
stablechannel) foundapiviagpm info api; prod (alsostable) reported it "not found." The channel was identical — the difference was the Grav core version, which silently filtered the plugin out.
Solution
The real cause is that prod was running the wrong Grav core. scripts/server-install.sh downloads grav-admin-v${GRAV_VERSION}.zip, and .env.prod still carried the stale pre-upgrade GRAV_VERSION=2.0.0-rc.10.
- Upgrade the Grav core in place to stable (rc.10 → 2.0.7):
make remote-upgrade-grav-prod # php bin/gpm self-upgrade -y && php bin/grav cache - Install the plugins now that a compatible core is present (the api plugin resolves):
make remote-install-plugins-prod # php bin/gpm index -f && php bin/gpm install <plugins.txt> -y # => "Preparing to install API [v1.0.8] ... Success!" - Clear cache, then verify the api route is live and login works:
make remote-clean-prod curl -s -o /dev/null -w '%{http_code}\n' https://site/api/v1/pages # 401 (was 404) => plugin installed + routed - Prevent recurrence: update
.env.prodtoGRAV_VERSION=2.0.4so a future fresh install doesn't reinstall rc.10 (self-upgrade fixed the running server, not the env file). Keep the api plugin's functional config in the trackeduser/config/plugins/api.yamlso it deploys on a clean clone.
Why This Works
GPM (Grav Package Manager) only offers a plugin version whose declared Grav requirement is satisfied by the installed core. The api plugin requires Grav >=2.0.4; on a 2.0.0-rc.10 core there is no compatible version, so GPM reports the package as "not found" rather than a version conflict. Admin2 declares api as a hard dependency and performs all authentication over the api plugin's /api/v1 JWT endpoints, so with api absent the login POST hits a route that doesn't exist (404) and never reaches Grav's auth layer — hence the silent failure with no grav.log entry. Upgrading the core to a stable >=2.0.4 build makes GPM offer api again; installing it registers /api/v1, and Admin2's login flow succeeds.
Prevention
- Keep
.env.<env>GRAV_VERSIONcurrent. It is the version a freshmake remote-install-<env>bakes in; a stale value silently installs an old core. After any core upgrade, bump the env file too — self-upgrade only moves the running server. Note this is a third version-authority surface alongsideuser/config/system.yamlgpm.releases(channel) andplugins.txt— they must stay in sync. A fourth surface governs the local Docker core: the hardcodedgrav-admin-v<ver>.zipURL inDockerfile..env.<env> GRAV_VERSIONgoverns fresh remote installs only — it never touches the local Docker core (which upgrades by an image rebuild, not self-upgrade). Seedocs/solutions/tooling-decisions/upgrade-local-grav-core-rebuild-docker-image.md. - When GPM says "package not found" for a package you know is on your channel, check the target's Grav core version first (
php bin/grav --versionon the server, ormake remote-diag-<env>). GPM filters by core compatibility; "not found" often means "no version compatible with your core," not "missing from the index."gpm index -fwill not help. - Don't trust a top-level install "Success" to mean dependencies installed. A fresh install can leave a plugin's declared dependency unsatisfied (here
admin2installed but itsapidependency didn't). Verify withls user/plugins/<dependency>. The samelsguards a second, distinct way a plugin ends up non-functional: its code folder can be missing while its config persists (e.g. in the per-host env tree), so it looks configured but never loads. Checkingls user/plugins/<name>catches both the missing-dependency and the config-without-code cases — seegrav-plugin-config-without-code-wont-enable.md. - Know the Admin2 ⇄ api coupling. Admin2 authenticates via the api plugin's
/api/v1endpoints; a missing or unrouted api plugin makes admin login fail silently (login POST 404s, nothing logged). A quickcurl /api/v1/pagesexpecting401(not404) is a good post-deploy smoke check.
Related
This is one of three independent gotchas from the same 2026-07-04 Grav 2.0.4 production cutover:
docs/solutions/integration-issues/grav-double-content-encoding-garbage-page.md— sibling: garbage-rendered pages from a doubleContent-Encodingheader on a non-FastCGI host. Different root cause (HTTP compression), same deploy.docs/solutions/test-failures/new-user-grants-api-not-admin-on-admin2.md— sibling: an authenticated account is denied an admin-gated page becauselogin new-userauto-detect grantedapi.*but notadmin.*. Different root cause (permission provisioning), same admin2/api area.docs/working/plans/2026-07-04-grav-2.0.4-upgrade.md— the upgrade plan whose Global Constraints spell out the GPM version floors (grav >=2.0.4,api >=1.0.6) that cause the "package not found" on an rc core.docs/solutions/conventions/grav-plugin-config-must-be-tracked-override.md— the other latent problem from this same investigation: the api plugin's functional config (enabled/route/session_enabled) must live in the trackeduser/config/plugins/api.yamlto deploy at all. Necessary but not sufficient here (the plugin must be installed first), but a durable convention in its own right.
A closely-related sibling in the "plugin absent/non-functional on prod" family (from the 2026-07-05 follow-up, not one of the three cutover gotchas above):
docs/solutions/integration-issues/grav-plugin-config-without-code-wont-enable.md— same outcome (a plugin inert on prod, fixed by a GPM install + cache clear), different trigger: there, GPM won't offer the plugin because the core is below the version floor; there, the plugin's code folder is simply missing while its config persists in the env tree (config-without-code desync). Samels user/plugins/<name>smoke check flushes both out.