--- title: "Admin2 login fails silently because a stale GRAV_VERSION installs an rc core below the api plugin's version floor" date: 2026-07-04 category: docs/solutions/integration-issues module: "grav / production deploy / plugin install" problem_type: integration_issue component: authentication severity: high symptoms: - "Admin2 login at /admin silently fails: button disables then re-enables, no visible error, nothing written to grav.log" - "admin2 SPA background login POST to /api/... returns 404" - "/api/v1/pages returns 404 on prod but 401 locally (api plugin route not registered)" - "user/plugins/api directory does not exist on prod (plugin never installed)" - "gpm install api reports 'These packages were not found on Grav: api' even after gpm index -f" root_cause: config_error resolution_type: environment_setup related_components: - "gpm" - "admin2 plugin" - "api plugin" - "scripts/server-install.sh" - "Makefile remote targets" - ".env.prod" tags: - grav - gpm - admin2 - api-plugin - plugin-dependency - version-compatibility - production-deploy - env-config --- # 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 `/admin` silently fails: the login button disables briefly, re-enables, and shows no error. **Nothing appears in `logs/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** with `content-type: application/json`. - `GET /api/v1/pages` returns **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/api` on the server: **No such file or directory** — the plugin was never installed, even though `admin2` (which depends on it) was. - `php bin/gpm install ... api -y` → `"These packages were not found on Grav: api"`, even after `php bin/gpm index -f`. ## What Didn't Work - **Committing/deploying the api plugin config** (`enabled` / `route` / `session_enabled`, moved from the untracked `user/plugins/api/api.yaml` into the tracked `user/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.4` requirement, so `api` is excluded from results entirely. - **Assuming "same channel = same availability."** Local (Grav 2.0.4, `stable` channel) found `api` via `gpm info api`; prod (also `stable`) 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`. 1. Upgrade the Grav core in place to stable (rc.10 → 2.0.7): ```bash make remote-upgrade-grav-prod # php bin/gpm self-upgrade -y && php bin/grav cache ``` 2. Install the plugins now that a compatible core is present (the api plugin resolves): ```bash make remote-install-plugins-prod # php bin/gpm index -f && php bin/gpm install -y # => "Preparing to install API [v1.0.8] ... Success!" ``` 3. Clear cache, then verify the api route is live and login works: ```bash make remote-clean-prod curl -s -o /dev/null -w '%{http_code}\n' https://site/api/v1/pages # 401 (was 404) => plugin installed + routed ``` 4. **Prevent recurrence:** update `.env.prod` to `GRAV_VERSION=2.0.4` so 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 tracked `user/config/plugins/api.yaml` so 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.` `GRAV_VERSION` current.** It is the version a *fresh* `make remote-install-` 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 alongside `user/config/system.yaml` `gpm.releases` (channel) and `plugins.txt` — they must stay in sync. A **fourth** surface governs the *local Docker* core: the hardcoded `grav-admin-v.zip` URL in `Dockerfile`. `.env. GRAV_VERSION` governs fresh **remote** installs only — it never touches the local Docker core (which upgrades by an image rebuild, not self-upgrade). See `docs/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 --version` on the server, or `make remote-diag-`). GPM filters by core compatibility; "not found" often means "no version compatible with your core," not "missing from the index." `gpm index -f` will 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 `admin2` installed but its `api` dependency didn't). Verify with `ls user/plugins/`. The same `ls` guards 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. Checking `ls user/plugins/` catches both the missing-dependency and the config-without-code cases — see `grav-plugin-config-without-code-wont-enable.md`. - **Know the Admin2 ⇄ api coupling.** Admin2 authenticates via the api plugin's `/api/v1` endpoints; a missing or unrouted api plugin makes admin login fail *silently* (login POST 404s, nothing logged). A quick `curl /api/v1/pages` expecting `401` (not `404`) 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 double `Content-Encoding` header 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 because `login new-user` auto-detect granted `api.*` but not `admin.*`. 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 tracked `user/config/plugins/api.yaml` to 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). Same `ls user/plugins/` smoke check flushes both out.