--- title: "cache.deleteAll() doesn't rebuild the page-tree index — a freshly-posted entry 404s when opened for editing" date: 2026-07-07 category: integration-issues module: cache-on-save problem_type: integration_issue component: plugin severity: high symptoms: - "A just-posted journal entry is written to disk but the API 404s on it (GET /api/v1/pages{route})" - "Opening the entry you just created for editing shows 'This entry no longer exists — it may have been deleted'" - "The entry DOES appear in the trip feed, but the edit prefill fetch can't find it until the next unrelated cache bump" - "Intermittent — only bites when the page-tree index survives the create" root_cause: incomplete_setup resolution_type: code_fix related_components: - documentation - development_workflow tags: - grav - cache - forms - page-tree --- # `cache.deleteAll()` doesn't rebuild the page-tree index ## Context — this is BUG-001 Part 2 [BUG-001](../../working/bugs-and-fixes.md) ("new entry not visible after form submission") was fixed by wiring `$this->grav['cache']->deleteAll()` into the `cache-on-save` plugin's `onFormProcessed` hook. That made new entries appear in the trip feed immediately. It was **not the whole story**: `deleteAll()` drops the Doctrine store (rendered-page cache, feed HTML, etc.) but does **not** force Grav to rebuild its **regular-pages index**. The gap only surfaced once the shared `/post` form gained an **edit mode** (`?edit=`), whose prefill does `GET /api/v1/pages{route}`. On a fresh create that request would 404 — so the owner opening the entry they had *just* posted saw "This entry no longer exists." ## Root cause Grav's regular-pages index is keyed on: ``` md5(dirs + folderHash + config->checksum() + lang) // Pages::buildRegularPages ``` With `cache.check.method: folder` (our setting), the `folderHash` component does not necessarily change when a new child folder is added inside an existing tree — so the **index key stays the same** and the stale index (missing the new entry) is reused. `deleteAll()` clears cache *stores* but does not change any of the inputs to that key, so the tree is not rebuilt. The new page is on disk and in the feed (which re-reads children), but the **API lookup by route** resolves through the cached index and 404s. ## Fix Add a second invalidation step alongside `deleteAll()`: ```php use Grav\Common\Cache; // ... $this->grav['cache']->deleteAll(); Cache::invalidateCache(); // touch(system.yaml) → bumps config->checksum() ``` `Cache::invalidateCache()` is lightweight and idempotent — it `touch()`es `system.yaml`, calls `clearstatcache()` and `opcache_reset()` (verified in Grav core `Cache.php`). Touching `system.yaml` bumps `config->checksum()`, which changes the index key, so the tree rebuilds on the next request and the new entry becomes resolvable by route. ### Latch it — the hook fires 4× per submit `onFormProcessed` fires once per `process:` action, and `post-form.md` has four (`add_page`, `upload`, `message`, `reset`). Without a guard the `deleteAll()` + `invalidateCache()` pair runs four times per post (a full store wipe + `system.yaml` touch each time). Gate it with a once-per-request latch (`$cacheInvalidated`), the same pattern already used for photo reconciliation (`$photosReconciled`). See `user/plugins/cache-on-save/cache-on-save.php`. ## How to verify 1. Post a new entry via `/post`. 2. From the trip feed, click the new card's **Edit** link. 3. The form prefills with the entry's title/body — no "no longer exists" banner. Regression test: `tests/ui/post/edit-mode.spec.js` **ES1** (create → open the feed card's Edit link → change title + body → Save → assert on disk). ## Residual coverage gap (tracked, not fixed here) `tests/ui/home/home.spec.js` **H1** and `tests/ui/maps/maps.spec.js` **M8** require `site.travelling: true` to exercise the active-trip home feed + home GPX map. The committed local `site.yaml` runs `travelling: false` (owner's testing config, intentionally not committed as `true`), so both specs **skip loudly** with a reason rather than fail misleadingly. They validate whenever the site is in travelling mode. This is a known gap in this environment, not a silent hole — provisioning `travelling: true` in a dedicated test config would close it.