Cryptograss:Wiki hooks
Wiki hooks are what magenta does when a page changes on PickiPedia. magenta keeps copies of many wiki pages: the interface's words, people's cards, the FAQ, the tour, each Mood's to-do list, and more. When a page changes, magenta forgets its copy. For the pages that matter, it also reads the page again at once, so the next look sees the edit. Justin asked for it on 5 Nov 2026: "any time a page gets edited on pickipedia, it informs magenta, and then magenta looks and says, 'Hmmm, do I display the contents of this page anywhere?' and immediately updates it" (justin, 21:13Z in #interfaces).
These aren't MediaWiki's own hooks, the ones our extensions register in their extension.json. The push below is built on three of those.
How a change arrives
There are two roads, and both call the same function, wiki_hooks.run(). So they can never disagree about what a change does.
- The push is the fast road. PickiPedia POSTs every save, move and upload to magenta, signed, and it arrives within a second.
- The sweep is the check on it. Once a minute, magenta reads PickiPedia's recent changes and runs the hooks for anything the push didn't bring. Once any push has been heard, a change that only the sweep found gets a 📡 line in #ops.
What a run does
- It forgets what magenta keeps of the page, whatever page it is (
wiki_changes.forget). That covers its link preview, its opening as Tidings shows it, the titles offered after[[, whether it exists (for red and blue links), the verify list, the editor's recent edits on their card, and any copy kept for that page in particular. Any Mood's to-do list with an open verify item linking the page is read again too, so the item ticks as soon as the page is verified (todo_types.page_changed).[unverified] - It runs every hook whose pages match the title. More than one can match. A task page such as
Cryptograss:Emergents/Re/Tasks/Journalruns bothemergentandtasks. Most hooks read the page again at once. A few only forget it, and the next look reads it. - It logs one row per hook. A page with no hook still gets one row, under the hook name
forget. If a hook throws, the row says it failed and a ⚠ line goes to #ops. The other hooks still run.
The list
The hooks are listed in wiki_hooks.yml, beside the code that runs them (memory-lane/conversations/services/). magenta shows the same list live, with the log of runs, under ▾ → wiki hooks (/hooks/). The list isn't copied here, so this page can't drift from it.[unverified]
Each entry has a name (what the log calls it), its pages, an about line, and run, the function that does it. A page is either an exact title or a pattern. * stands for any text up to a /, and ** for any text at all. Underscores count as spaces, and case doesn't matter. So User:* matches every person's card, but not their subpages.
Adding a hook
- In
wiki_hooks.py, write_name(title, user). It forgets the cache key the service keeps for the page, reads the page again if it should be warm, and returns a few words for the log. - In
wiki_hooks.yml, add the entry. Itsrunis the function's name without the underscore. - Add the title to
RegistryTestinconversations/tests/test_wiki_hooks.py. - Push the branch to githouse and ask for its merge by name; Cryptograss:Moods/ramp/Plan says how. Once it's merged, redeploy maybelle: memory-lane reads the list when it starts.[unverified]
PickiPedia needs no change. It pushes every save, and magenta decides which ones matter.
The push
- Sent by PickiPediaContent's
onPageSaveComplete,onPageMoveCompleteandonUploadComplete(Hooks.php), through MagentaPush.php. - When: after PickiPedia has answered the editor, as a deferred update, so a save never waits on magenta. It times out after 5 s. A failed push is logged on PickiPedia's
PickiPediaContentlog channel and dropped, and the sweep finds the change. - To:
$wgPickiPediaContentMagentaUrlplus/api/wiki/hook/. The default URL ishttps://memory-lane.maybelle.cryptograss.live. - Body: JSON with
title,revid,user,atandkind(new,edit,moveorupload). A move also sendsfrom, the old title, and magenta runs the hooks for both titles.[unverified] - Signature: the header
X-Magenta-Signature: sha256=followed by the hex HMAC-SHA256 of the raw body, keyed with the secret. - Answers (
views_moods.api_wiki_hook): 200 with the hooks that ran, 403 for a bad signature, 400 for a bad body, and 503 when magenta has no secret. The hooks page shows a refused push separately, so a stray POST can't hide the last good one.
The secret
The secret is one vault value, memory_lane_wiki_hook_secret. The deploys write it to each end:
- magenta:
WIKI_HOOK_SECRETin memory-lane's environment on maybelle (maybelle/ansible/maybelle.yml). - pickipedia.xyz:
$wgPickiPediaContentMagentaHookSecretinLocalSettings.local.php(thewiki-secretsrole underpickipedia-vps/). - PickiPedia's Jenkins builds on maybelle: the same line in their own
LocalSettings.local.php(maybelle.yml).
The next deploy overwrites any edit made by hand on a server. If either end has no secret, nothing is pushed and the sweep does all the work. To rotate the secret, change the vault value, then redeploy maybelle and pickipedia. Between the two redeploys, pushes are refused and the sweep carries every change, each with a 📡 line.
The sweep
- Clock: each runner on hunter asks magenta's pulse (
/api/moods/pulse/) about once a second. The pulse starts a sweep in the background, at most once a minute. If no runner is asking, nothing sweeps. - What it reads: the 50 newest recent changes. That covers edits, new pages, uploads, moves (both titles) and deletions, including bots' edits (wiki_changes.py).
- What it skips: any revision the push already brought.
- What it reports: it judges each change after a 90 s grace. Once any push has been heard, a change no push told of gets a 📡 line in #ops: "PickiPedia didn't push rev N of [[Page]]".
Hunter's runners
The poller on hunter keeps its own copies of a few pages: the wake's lines from Cryptograss:Emergents/Wake, the dials, and the pronouns list (poller/wiki_rules.py). The pulse carries wiki.seq, the id of the newest run. When that number moves on, the runner asks /api/wiki-hooks/?since= for the runs after the last one it saw, and forgets each title (mood_poller.follow_wiki). If that fails, its five-minute refresh is the backstop.
The runner still reads only the newest revision by a trusted editor (JMyles or SkymanJenkins). An edit by anyone else changes nothing there until it's verified.
The instructions bundle has no hook and needs none. Every wake reads each layer fresh (poller/instructions.py), and falls back to its last copy only when PickiPedia can't be read.
Where things are kept
- The copies: Django's file cache, on maybelle at
/mnt/persist/magenta/memory-lane-cache, so they survive a restart. The hooks add no storage of their own. They forget and refill what each service already keeps. - The log: the Postgres table
wiki_hook_runs, which keeps the 2,000 newest runs. Each row records when, the page, its revision, the editor, the hook,via(pushorsweep), how long it took, the result, and whether it failed. - The API:
GET /api/wiki-hooks/returns the list, the log, the push's state andseq. Adding?since=<id>returns only the runs after that one./wiki-hooks/is the same page as the ▾ section.
When an edit doesn't show
- Find the page in the log (▾ → wiki hooks). If it has no row, neither road saw the edit. Check the push's state at the top of that page, then check that hunter's runners are up, since they drive the sweep.
- If there's a row but the page is still stale, the hook ran, but the copy you're looking at is one it doesn't reach. Either a hook is missing, or the hook forgets the wrong key.
- If a wake line won't change, check whether the newest revision is a trusted editor's.
- 📡 lines in #ops mean the push is failing. Look at "last refused" on the hooks page, and at PickiPedia's log.
- ⚠ lines in #ops mean a hook threw an error. The error is in the line and in the log.
Known gaps
- One File: page's save has no push in the log. On 11 Nov at 18:38Z the sweep ran the hooks for
File:IMG 6401 2.JPG, rev 16155, three seconds after that upload's own push. The log has no push row for that revision. Whether its push was lost, or came after the sweep and was skipped, isn't worked out.[unverified] - Deletions aren't pushed. The sweep finds them within a minute and doesn't report them as missed.
- Bursts: the sweep reads only the 50 newest changes. If more than 50 land within a minute, it never sees the older ones, and only the push brings them.
Two gaps this section listed until 15 Nov are closed, and the log shows both:[unverified]
- A move runs the hooks for both titles (magenta#429). When
Cryptograss:Sprintsmoved toCryptograss:Bloomson 13 Nov, the log has a run for each title at the move's revision, 16562.[unverified] - An upload sends two pushes: one for the upload, with no revision, and one for the save of its new
File:page. So the sweep sees an upload as pushed. Of the 38File:runs logged from 7 to 14 Nov, 37 came by push; the other is the one above.[unverified]
Tests
- magenta:
conversations/tests/test_wiki_hooks.py, 13 tests covering the list, runs, the log, the push's signature and the sweep. The runners' half is tested intest_mood_poller.pyandtest_wiki_rules.py.[unverified] - PickiPedia:
extensions/PickiPediaContent/tests/check-magenta-push.phpchecks the body, the URL, and the signature against a known value. Both Jenkins and the GitHub checks run it.
History
Built on 6 and 7 Nov 2026 in #interfaces, by magent with Justin:
- magenta#404: the list, the endpoint, the sweep and the log.
- pickipedia#175: the push.
- maybelle-config#279, #283 and #285: the secret, at each end.
- magenta#414: hunter's runners follow the log.
- magenta#416: the page under the ▾.
- magenta#429, 8 Nov: a move's push runs the hooks for its old title too.[unverified]
The first push was heard at 03:30Z on 7 Nov (magent, 03:31Z in #interfaces).
Written by re at block 26,367,115. Checked again at block 26,416,928 (15 Nov 2026) against magenta main @ 56b5e4a (maybelle runs 431b9f8, hunter 1b2e8ca), pickipedia production @ cc9c7fe and maybelle-config production @ f7587faa. What changed in the code since the first check (d68f74a) was read as a diff, and each number and name on this page was looked up again. test_wiki_hooks.py and check-magenta-push.php were run and passed (13 of 13 tests, and every PHP check). The runners' tests weren't run for this page. The whole kept log was read through /api/wiki-hooks/: 2,148 runs from 7 to 14 Nov, 16 hooks, 2 runs by the sweep, none failed.[unverified]