Skip to content

Add a runbook section for each Piri proving alert - #178

Merged
Peeja merged 57 commits into
mainfrom
claude/happy-bell-cfh5qb-proving-runbook
Oct 23, 2026
Merged

Peeja merged 57 commits into
mainfrom
claude/happy-bell-cfh5qb-proving-runbook

Conversation

@Peeja

@Peeja Peeja commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Written by Claude.

This adds a "Piri's proving alerts" section to docs/RUNBOOK.md, under "When something is wrong", with one subsection per Piri proving rule. Each subsection says how soon to act and why, then gives numbered steps: what to run, how to read the result, and what to do for each outcome. A "Before you start" subsection holds the setup every entry shares, and "Free stranded batches" is its own subsection that the others link to. As Petra asked in review, this is a pilot for the step-list style; the rest of the runbook can follow in a separate PR.

The companion change, fil-forge/infra-central#249, merged on 2026-10-20 and points each rule's runbook_url at its subsection. Until this merges, those live links land at the top of the runbook.

Rule Act Why
Piri's chain head is stale within about three hours AddRoots gives up on each batch after 50 retries, about 3.5 h (6.9 h if Lotus answers but is stale), and nothing claims it again (FIL-1508).
Piri is reporting but has no chain head within about three hours Same.
Piri proving is failing now Curio gives up on a proof set for good on its fifth rejection, as soon as 12.5 h after the first.
Piri's proving period has not advanced within a day About one proving period's payment a day, and it catches up once fixed, except when AddRoots is failing too with the head moving (an empty wallet, for example); the section says to check the wallet and look for stranded batches.
Piri's Prove task has not run in 1.5 proving periods within a day Same, with the same exception.
Piri proof set is unrecoverable within a day The loss has already happened.

"Free stranded batches" has a query that counts stranded AddRoots batches (and their roots) and an UPDATE that frees them. The tables are in the curio schema of each Piri's piri_<n> database (HarmonyDBSchema in piri's pkg/curiopdp/schema/schema.go), reached the way this runbook's existing psql example does. I haven't run either statement on a node. The UPDATE only touches rows whose task no longer exists, so it doesn't free a batch a running task holds.

Checks: make check-shell, git diff --check. Docs only.

✴️

https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ


Generated by Claude Code

infra-central's proving rules now link here, one section each. Each
section says how soon to act and why: the chain-head rules within about
three hours, before AddRoots strands its batches (FIL-1508), with the
query that finds and frees them; "proving is failing" now, before the
fifth rejection ends the proof set; the rest within a day, since they
cost about one proving period's payment a day and catch up once fixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ
Copilot AI balanced review requested due to automatic review settings October 15, 2026 21:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

- The warning sections say how an AddRoots failure with a moving head,
  such as an empty wallet, can strand batches, and to check for them.
- The proving-failing section no longer offers the wallet as its fix.
- Retry totals are 3.5 and 6.9 hours.
- The stranded query counts batches as well as roots.
- Restarts name docker restart filone-piri.
- The intro no longer says all three warnings cost a period a day.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ

Peeja commented Oct 15, 2026

Copy link
Copy Markdown
Contributor Author

Claude reviewer says… (1st round, 2cb0f5d)

Approve after one should-fix. This round covered this PR together with fil-forge/infra-central#248 and #249.

Verified

  • All six anchors match the runbook_urls in infra-central#249.
  • SQL targets: HarmonyDBSchema = "curio", database piri (PIRI_DB_POSTGRES_URL on dev and staging), and filone-platform-postgres-1 on both stages (both platform composes are name: filone-platform). The SQL was not run.
  • Numbers: the 12.5 h figure is right, and the AddRoots figures are off by one (below). The timeframes match the alert descriptions.
  • make check-shell and git diff --check pass.

Findings

  1. should-fix, the "Prove task has not run" and "proving period has not advanced" sections say the causes that can lose more page on their own. An AddRoots failure with a moving head, such as an empty wallet, strands batches with only warnings firing (see infra-central#249's round 4).
  2. nit: the retry totals are about 3.5 h and 6.9 h, not 3.4 h and 6.7 h.
  3. nit: the stranded-batch query counts roots, not batches.
  4. nit: the intro says all three warnings cost a period a day; the unrecoverable one doesn't.
  5. nit: "A wallet without enough FIL for gas is the cause the node can fix" sits under "proving is failing", but an underfunded send probably isn't counted as a rejection (speculation).
  6. nit: "restart Piri" gives no command, and doesn't say whether the deploy proving-window gate matters.

✴️


Generated by Claude Code

Peeja commented Oct 15, 2026

Copy link
Copy Markdown
Contributor Author

Claude author says…

Fixed in c6d4951.

  1. Wallet case: both warning sections now say a cause that also stops AddRoots while the head moves, such as an owner wallet without FIL, can lose more without paging. Each says to check the wallet's balance, and to look for stranded batches once it's fixed.
  2. Sums: 3.5 h and 6.9 h.
  3. Query: now returns count(DISTINCT s.add_task_id) AS batches, count(*) AS roots.
  4. Intro: two of the warnings cost about a period a day; the third reports a loss that has already happened.
  5. Wallet advice: removed from "proving is failing". That section now says to read the revert reason.
  6. Restart: both places give docker restart filone-piri. The chain-head section says the deploy gate doesn't apply, since Piri isn't proving without a head.

Checks: make check-shell, git diff --check.

Round 1 had a should-fix, so a round 2 follows.

✴️


Generated by Claude Code

claude and others added 3 commits October 16, 2026 14:15
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ
The reset script that just landed names the per-volume layout's
containers (filone-piri-N) and databases (piri_N).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ

Peeja commented Oct 16, 2026

Copy link
Copy Markdown
Contributor Author

Claude reviewer says… (2nd round, 480f667)

Approve. Nits only. This round covered this PR and fil-forge/infra-central#249 together.

Verified

  • Round 1's fixes are in: the wallet case, the 3.5 h and 6.9 h totals, the batch count, the restart command, and the intro.
  • All anchors and in-page links resolve. "Piri crash-loops on the chain endpoint" exists.
  • The SQL matches 00000003-blob-pipeline.sql; it was not run.
  • The per-volume names match reset-apps.sh (L29, L103).
  • make check-shell check-compose and git diff --check pass.

Findings

  1. nit, PR body table: the proving-period and Prove-task rows don't mention the AddRoots/wallet exception.
  2. nit, RUNBOOK.md:1168-1170: the per-volume note says "below" but also applies to the commands just above it. It also pairs filone-piri-N with piri_N, which reset-apps.sh doesn't establish.
  3. nit, RUNBOOK.md:1234 and 1244: "Check the wallet's balance" gives no command or pointer.

✴️


Generated by Claude Code

Peeja commented Oct 16, 2026

Copy link
Copy Markdown
Contributor Author

Claude author says…

Fixed in 1b2b13e.

  1. Table: both rows now name the exception.
  2. Per-volume note: now "in place of filone-piri here and below". It no longer pairs the names, and it says to read each container's PIRI_DB_POSTGRES_URL with docker inspect filone-piri-<n>.
  3. Wallet: both sections now point to step 6 for funding it. The paragraphs are rewrapped.

Checks: make check-shell check-compose, git diff --check.

This round found only nits, so there won't be another.

✴️


Generated by Claude Code

claude and others added 11 commits October 16, 2026 15:22
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ
Since #171, staging runs filone-piri-0 to filone-piri-2 with databases
piri_0 to piri_2, like dev, so the note on which Piri to check applies
to both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ
The section used `filone-piri` and `piri` as placeholders to substitute.
Staging may still hold a database named `piri` from its single-Piri
days, so an unsubstituted psql would read and update a dead database
without an error. The commands now take the Piri's number in `n`
(`filone-piri-$n`, `piri_$n`) and fail if it is unset. The wallet checks
say each Piri has its own owner wallet, at OpenBao path `piri-$n`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ

Peeja commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

Claude reviewer says… (round after #171, d8833ba)

One should-fix and one nit. The rewritten paragraph is accurate, but staging makes the piri database placeholder risky.

Verified

  • Dev's and staging's composes both define filone-piri-0 to -2, with PIRI_DB_POSTGRES_URL pointing at piri_0 to piri_2. Both platform composes create exactly those databases.
  • Postgres is still filone-platform-postgres-1 with user admin on both nodes, and the section's anchors resolve.
  • Nothing in this section still says staging has one Piri. The remaining single-filone-piri lines elsewhere in the RUNBOOK were already on main.
  • make check-shell passes and git diff --check is clean.

Findings

  1. should-fix, docs/RUNBOOK.md:1200,1206 with the substitution sentence at 1179. feat(staging): run three Piris on the shared disk #171 kept staging's old Piri as piri-0: keygen.sh's adopt_single_piri copies the OpenBao piri secret to piri-0. It also moved that Piri's database from piri to piri_0. Nothing drops the old database, and the Postgres volume persists. So staging very likely still has a piri database holding Curio state for piri-0's DID; I haven't checked the live host. A reader who runs the stranded-batch block without substituting gets phantom results, and the UPDATE "frees" rows in a dead database, all without an error. The filone-piri placeholder is safe by comparison, because no such container exists and the command fails. "Substitute … for piri" is also ambiguous, since piri is the CLI too. Suggested fix: take the Piri's number in a variable (filone-piri-$n, piri_$n) everywhere, so an unset variable fails loudly.
  2. nit, docs/RUNBOOK.md:1243,1252. "Check the wallet's balance" now means one of three wallets. Each Piri has its own (OpenBao piri-<n>). Step 6's direct lookup on main still reads path piri, but that's outside this PR.

Not findings: "grouped by node, not by Piri" (a known follow-up after fil-forge/infra-central#270).

✴️


Generated by Claude Code

Peeja commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor Author

Claude author says…

Fixed in 0ef0aaa.

  1. Placeholders. Confirmed: keygen.sh copies piri to piri-0, staging's postgres-init now ensures only piri_0 to piri_2, and nothing on main drops piri; only reset-apps.sh drops databases. The section now takes the Piri's number in n (filone-piri-$n, -d "piri_$n"), and the substitution sentence is gone. With n unset, docker and psql both fail rather than reach the wrong Piri. The section says staging may still hold a piri database, because I can't see the host.
  2. Wallet. Both wallet checks now say each Piri has its own owner wallet at OpenBao piri-$n. Step 6's lookup on main reading piri is left as is: it's outside this PR, and I've noted it for Petra.

Checks: make check-shell and git diff --check. This round had a should-fix, so another round follows on 0ef0aaa.

✴️


Generated by Claude Code

`docker logs filone-piri-$n 2>&1 | grep …` with n unset printed nothing:
grep swallowed docker's error, which reads like a Prove task that never
ran. It now says `${n:?}`. The wallet checks also say the path is
`piri-$n`, not the `piri` that step 6's direct lookup reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ

Peeja commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

Claude reviewer says… (second round after #171, 0ef0aaa)

The $n change fixes the last round's should-fix, but one command still fails silently, and two nits remain.

Verified

  • Every command in the section uses $n, and no bare filone-piri or -d piri is left.
  • $n appears only in words the host shell expands, never inside single quotes or in the container. piri status and piri client … run inside the container and don't need it.
  • The OpenBao path is right. piri_services yields piri-0 to piri-2, and keygen.sh stores each Piri's owner_wallet_* under its own path.
  • Piri sends AddRoots from the owner address, so an empty owner wallet does stop AddRoots. I checked this in a local piri checkout, which may not match the deployed image.
  • The reflow is clean: no line is over 100 columns, both anchors resolve, and the code block is intact. make check-shell passes and git diff --check is clean.

Findings

  1. should-fix, about line 1181. With n unset, docker logs filone-piri-$n 2>&1 | grep PDPv0_Prove prints nothing, because 2>&1 hands docker's "No such container" error to grep, which drops it. Empty output is exactly what the "Prove task has not run" alert sends the reader to look for. So "Left unset, they fail" isn't true for this command. Use ${n:?} there.
  2. nit, about line 1178. "Left unset, they fail" dangles. "If n is unset, these commands fail" says what it means.
  3. nit. Both "fund it as step 6 describes" links lead to step 6's direct lookup, which on main reads OpenBao path piri, not piri-$n. Saying so beside the links closes the gap inside this PR. Step 6 itself, and its claim that the node pays only for registration, are main's.

✴️


Generated by Claude Code

Peeja commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

Claude author says…

All three are fixed in 0f2aa57.

  1. The log search is now docker logs "filone-piri-${n:?}" 2>&1 | grep PDPv0_Prove, with one sentence explaining why only that command needs ${n:?}. In a shell with n unset, ${n:?} aborts with "n: parameter not set or null" before docker runs.
  2. Reworded to "If n is unset, these commands fail rather than reach the wrong Piri."
  3. Both wallet checks now name piri-$n, and the first says it isn't the piri that step 6's direct lookup reads. Step 6 itself stays as it is, since it's main's; it's noted for Petra.

Checks: make check-shell and git diff --check. This round had a should-fix, so another round follows.

✴️


Generated by Claude Code

grep now also matches 'No such container', so a wrong `n` prints
docker's error rather than nothing. The Prove-task entry's wallet check
says to read `piri-$n` in place of step 6's `piri`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ

Peeja commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

Claude author says…

Fixed in 18ecd09, except 7.

  1. Checked against curio: only errors IsContractRevert matches count toward this alert. I dropped the branch, and the entry now says the node's owner wallet isn't the cause, and why.
  2. Any revert now goes to whoever runs infra-central, with the revert reason, the proof set's ID and its state. That's the phrasing the runbook already uses for PAYER_ADDRESS, and central sets up the data set's payments. @Peeja: if there's a specific person or channel to page, I'll name it.
  3. After the token check, the no-head entry now returns to its own step 2. It also has an "any other error" branch.
  4. The key check now says "on the host". A timeout or another status sends the reader to the host's network and Chain.Love's status, then back to step 1.
  5. A Current Epoch of 0 now says the proof set isn't initialised, and points to reading the head from an initialised proof set (--proof-set with an ID from proofset list). Only if there's none does it go to the restart.
  6. piri status failing or reporting unhealthy means restart. Healthy goes on to the logs, since the engine can stall behind a healthy node. Step 1 now checks both head alerts.
  7. Left for the separate style PR, as suggested: it's text on main outside this section.
  8. The log command moved out of the table into its own code block, and every line is within 100 columns.

I also added a line telling the reader to keep a host terminal open, with the same n set, next to the container shell, since several steps run on the host.

Checks: make check-shell and git diff --check pass. Round 4 had should-fixes, so a round 5 follows.

✴️


Generated by Claude Code

claude and others added 2 commits October 23, 2026 18:54
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ
infra-central#270 merged, so the chain-head and Prove-task alerts now
carry the Piri's instance, and the proving alerts already carry its
proof set. Say how to get n from each, and keep "work through each
Piri" for the unrecoverable alert, which names only the node.

Review round 5 of the step rewrite:
- A working key check prints the 101 line and then curl's timeout, so
  only a missing HTTP line counts as unreachable.
- piri status makes the same call as proofset state and is never
  unhealthy when it answers. Its errors, and the "any other error"
  branches, now go to one list in Before you start that says where
  each one points: the chain endpoint, Piri's database, or the Piri
  not answering.
- A Current Epoch of 0 also covers a proof set Curio has given up on.
- The no-head entry runs only the stale entry's key check, not its
  go-to steps, and the Prove-task entry's reused step no longer names
  the other entry's step number.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ

Peeja commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

Claude reviewer says… (5th round, 18ecd09)

18ecd09 fixes round 4's three should-fixes, and both code claims hold up. The new branch wording has two minor should-fixes, plus four nits. Nothing is blocking.

Verified

  • Unfunded owner wallet: gas estimation doesn't fail; the mpool rejects the send with "not enough funds". IsContractRevert doesn't match that, so Curio retries it (Tier 3 in HandleProvingSendError) and consecutive_prove_failures doesn't rise. Only Tiers 1 and 2 count toward this alert. Speculation: this assumes Chain.Love behaves like Lotus here.
  • Payment ended: this is Tier 1, so it does raise this alert, which is consistent with the entry.
  • Current Epoch of 0: an uninitialised proof set returns only its ID, so Current Epoch reads 0. --proof-set with another, initialised ID reads the same head.
  • The six headings are unchanged and all in-section anchors resolve. make check-shell and git diff --check pass. No line in the section is over 100 columns.

Findings

  1. should-fix: the new "A timeout or any other status" branch catches a healthy key check. A working key prints 101 and then curl's timeout, so the reader loops back to step 1.
  2. should-fix (minor): "any other error: … Check piri status and the logs" is a dead end. piri status makes the same GetProofSetState call, and "the logs" names no pattern. failed to get contract state is also the chain endpoint, through eth_call.
  3. nit: "or reports the node unhealthy" can't happen: piri status returns Healthy: true whenever it answers.
  4. nit: "which step 4's logs show" points at the wrong step when the Prove-task entry reuses that step.
  5. nit: "not initialised yet" also covers a proof set Curio has given up on, since both paths set init_ready = FALSE.
  6. nit: "check the token as in step 2 … then come back" sends the reader into the other entry's "go to" branches.

✴️


Generated by Claude Code

Peeja commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

Claude author says…

All six are fixed in 64f1918. It also merges main, and it updates the section now that fil-forge/infra-central#270 has merged.

  1. Only a missing HTTP/1.1 line, curl's timeout on its own, or another status counts as "can't reach Chain.Love". A note says the timeout after 101 is expected.
  2. I checked this against piri's code. piri status wraps GetProofSetState (pkg/client/status.go:40), and proofset state fails in one of three places: the chain (head or contract state), the database (no proof set found, failed to retrieve proof set), or the node not answering. "Before you start" now has step 4, which says what to do for each. Every "any other error" branch, and a failing piri status, points there. failed to get contract state routes to the chain-endpoint steps.
  3. "Unhealthy" is gone. If piri status answers, the reader goes on to the logs.
  4. The wording is now "which the logs show", with no step number.
  5. A Current Epoch of 0 now says "not initialised, or Curio has given up on it".
  6. The no-head entry now says to run only the key check, "ignoring that entry's 'go to' steps".

After #270: the chain-head and Prove-task alerts now carry the Piri's instance, and the proving-failing and proving-period alerts carry its proof_set. "Before you start" now says how to get n from each. "Work through each Piri" stays only for the unrecoverable alert, which names only the node.

Checks: make check-shell and git diff --check pass, and no line is over 100 columns. Round 5 had should-fixes, so a round 6 follows.

✴️


Generated by Claude Code

Review round 6 found the error list routing two cases wrong:
- "no proof set found" is a database that answered without the row:
  a proof set ID from another Piri, or a data set Curio deleted. Check
  the ID against proofset list, and record a deleted data set.
- The chain branch sent readers into the stale-head entry's later
  steps from entries where no head alert is firing. Run only the key
  check, restart Piri, and return to the entry the reader came from.
Everything else is now a restart, with how long health: starting lasts,
the reboot case, and what to record if the error survives it, instead
of guessing which component failed.

Also: n=1 is now an example tied to its instance, and the loop that
finds a proof set's Piri is spelled out, with its own variable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ

Peeja commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

Claude reviewer says… (6th round, 64f1918)

Round 5's six findings are fixed. The new error list in "Before you start" step 4 has two minor should-fixes, and there are three nits. Nothing is blocking.

Verified

  • Grouping on infra-central main after #270: the two chain-head rules and the Prove-task rule group by instance. The proving-failing and proving-period rules carry proof_set. The unrecoverable rule groups by node only. The instance format is ${FILONE_NODE}/piri-<n>.
  • Error strings: they match proofset_state.go and reach the CLI as text.
  • Postgres filter: docker ps --filter name=postgres matches filone-platform-postgres-1.
  • Links and steps: all in-section links resolve, #before-you-start is unique, and no reference points at the old step 4.
  • Checks: make check-shell and git diff --check pass, and no line is over 100 columns.

Findings

  1. should-fix: no proof set found isn't a database failure. It's a 404 from a database that answered without the row. A healthy Piri returns it for another Piri's proof set ID, or after Curio has deleted a terminated data set (watch_data_set_delete.go). Checking that Postgres is up doesn't help in either case. Also, that Postgres is this node's, so central is the wrong place to send a real database error. And Postgres being down has no branch.
  2. should-fix (minor): "Follow Piri's chain head is stale from step 2" sends readers who arrive from the proving-period or Prove-task entry into the stale-head entry's restart and "wait for the alert to clear" steps, when no head alert is firing. A token check also tests nothing for failed to get contract state, which can only follow a successful head read.
  3. nit: "Set the Piri's number: n=1" reads as a literal instruction.
  4. nit: the proof_set bullet expects a shell in each Piri before steps 1–2 explain how. A host-side loop is simpler.
  5. nit: "Anything else" has no guidance on health: starting (up to the 180 s start_period), or on a restart that fails after a reboot.

✴️


Generated by Claude Code

Peeja commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

Claude author says…

Fixed in 5e50d2a. This is the third round running in which the same area has drawn a should-fix, each one in wording the previous fix added. So rather than refine the routing again, I've cut step 4 back to what each error actually tells the reader.

  1. no proof set found now says the database has no proof set with that ID. Check the ID against piri client pdp proofset list. If the one in piri-config.toml isn't listed, the data set has been deleted: record it as for an unrecoverable proof set. The Postgres and central branch is gone.
  2. failed to get chain head now runs only the key check, ignoring the stale-head entry's "go to" steps. Then it restarts Piri and returns to the entry the reader came from. failed to get contract state is no longer routed to the chain.
  3. Now "for example n=1 for staging/eu-central-3/piri-1".
  4. Added a host-side loop that lists each Piri's proof sets. It uses i, so it doesn't overwrite n.
  5. Every other error is a restart. The entry says health: starting lasts up to three minutes, that a restart failing on a missing file means the node rebooted (with a link to "The node rebooted"), and to record the full error if it survives a healthy restart.

Checks: make check-shell and git diff --check pass, and no line is over 100 columns. Round 6 had should-fixes, so a round 7 follows.

✴️


Generated by Claude Code

Review round 7: Piri's healthcheck has a 180 s start period and 30
retries 10 s apart, so health: starting can last about eight minutes,
not three. A restart that ends unhealthy or keeps restarting now goes
to docker logs, where a missing file or "is a directory" means the
node rebooted. Each branch of the error list ends by saying where to
go next, a container that isn't running points at the restart, and
the proving-failing entry reads proofset state errors like the others.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ

Peeja commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

Claude reviewer says… (7th round, 5e50d2a)

Round 6's two should-fixes are fixed. There's one new minor should-fix in the "Anything else" branch, and three nits. Nothing is blocking.

Verified

  • A deleted data set gives no proof set found. Curio's watch_data_set_delete.go deletes the pdp_data_sets row, and Piri returns that error on ErrNoRows. proofset list reads the same table.
  • A terminated payment sets the unrecoverable epoch, which the unrecoverable alert reads.
  • Piri's bind mounts use create_host_path: false, so a restart after a reboot fails on a missing source. That matches "The node rebooted".
  • The chain branch no longer goes into the stale-head entry's later steps. The new host loop uses i and leaves n alone. All links resolve, and step references are correct.
  • make check-shell and git diff --check pass, and no line is over 100 columns.

Findings

  1. should-fix (minor): "up to three minutes" is wrong. With start_period: 180s, interval: 10s and retries: 30, health: starting can last about eight minutes. A restart that ends unhealthy or crash-loops has no next step. One example is the read /keys/piri.pem: is a directory failure in "The node rebooted", where docker restart succeeds but the container then fails health.
  2. nit: after a reboot, docker exec into the container fails before step 4 can help.
  3. nit: only the chain branch says to return to the entry you came from.
  4. nit: "Piri proving is failing" step 2 doesn't point at step 4 for a failing proofset state.

✴️


Generated by Claude Code

Peeja commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

Claude author says…

All four are fixed in c364743.

  1. "Up to about eight minutes before Docker gives up on it". The restart now has three outcomes:
    • healthy and the error is gone: go back;
    • the restart fails, ends unhealthy, or keeps restarting: read docker logs, and if it shows a missing file or is a directory, follow "The node rebooted";
    • healthy but the error returns: record it.
  2. Step 2 of "Before you start" now says that if docker exec reports the container isn't running, restart it as in step 4.
  3. Every branch now ends by saying where to go next.
  4. Step 2 now reads errors through "Before you start", step 4, as the other entries do.

Checks: make check-shell and git diff --check pass. Round 7 had a should-fix, so a round 8 follows.

✴️


Generated by Claude Code

A reboot's missing bind-mount source fails in the Docker daemon before
Piri starts, so it shows in docker restart's output and State.Error,
not in docker logs. Also name the step-4 bullet step 2 points at, and
say Docker marks the container unhealthy rather than giving up on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ

Peeja commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

Claude reviewer says… (8th round, c364743)

c364743 fixes all four round-7 findings. One should-fix in the new restart branch, plus two nits; nothing blocking.

Verified: the healthcheck in both compose files (start_period: 180s, interval: 10s, retries: 30, restart: unless-stopped, no autoheal), so starting lasts about 480 s, longer if probes hang to the 5 s timeout; each round-7 nit is addressed; no loops or wrong step numbers; $n only in host commands and piri only in the container; every in-section link and all six runbook_url anchors in infra-central's alerts.tf resolve; make check-shell and git diff --check pass. Nothing was run on a node.

  1. should-fix, docs/RUNBOOK.md:1327-1329. Every Piri bind mount sets create_host_path: false (e.g. nodes/dev/apps/compose.yml:94-111), so after a reboot the start fails in the Docker daemon before Piri runs (bind source path does not exist). That shows in docker restart's output and State.Error, not in docker logs, so the reader would take "Otherwise record the logs" and never reach "The node rebooted". The is a directory case is right, since Piri logs that itself. (Reasoned from moby's mount validation, not run.)
  2. nit, docs/RUNBOOK.md:1303. "step 4's last bullet" is now ambiguous, since step 4 has nested bullets.
  3. nit, docs/RUNBOOK.md:1325. "before Docker gives up on it": Docker only marks the container unhealthy and keeps probing.

✴️


Generated by Claude Code

Peeja commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

Claude author says…

Fixed in 213506a.

  1. Holds: the compose files set create_host_path: false on every Piri mount, and "The node rebooted" already says each restart "fails on a missing bind-mount source". The branch now says to read the error docker restart printed, or docker inspect -f '{{.State.Error}}' filone-piri-$n, as well as docker logs; bind source path does not exist or is a directory routes to "The node rebooted", and otherwise both go on the incident.
  2. Now "step 4's Anything else bullet".
  3. Now "for about eight minutes, or longer if its probes time out, before Docker marks it unhealthy".

Ran make check-shell and git diff --check, and checked no line in the section passes 100 columns. Doc-only, so nothing else.

Round 8 had a should-fix, so a round 9 follows.

✴️


Generated by Claude Code

Rounds 8 and 9 disagreed on the exact error a restart prints after a
reboot, and neither was run on a node. Whether piri-$n.pem is missing
or a directory on the host answers the question without depending on
Docker's wording. Also cover a container that is restarting, and send
a reader from step 2 back to step 2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ

Peeja commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

Claude reviewer says… (9th round, 213506a)

The round-8 fixes landed, but round 8's diagnosis looks wrong, so the new wording still misroutes a rebooted node. One should-fix and two nits; nothing blocking.

Verified: create_host_path: false on all 11 bind mounts and the healthcheck values match the text; "marks it unhealthy" is accurate; State.Error is the right field (cleared on a later successful start, harmless since the bullet also reads the logs); "step 4's Anything else bullet" is unambiguous; all six runbook_url anchors and in-section links resolve; make check-shell and git diff --check pass; the branch merges cleanly with main. Nothing was run on a node.

  1. should-fix, docs/RUNBOOK.md:1328-1331. In moby, bind source path does not exist comes only from mount validation at container create. A start or restart of an existing container with create_host_path: false skips that check, and the missing file fails in runc as error mounting … no such file or directory. A reader on a rebooted node matches neither listed string and records the error instead of reaching "The node rebooted". (Reasoned from moby and runc source, not reproduced.)
  2. nit, docs/RUNBOOK.md:1302-1303. A crash-looping container gives is restarting, wait until the container is running from docker exec, which "is not running" doesn't cover.
  3. nit, docs/RUNBOOK.md:1309-1310 vs 1324. A reader sent to step 4 from step 2 has no error to see "gone"; tell them to retry step 2.

✴️


Generated by Claude Code

Peeja commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

Claude author says…

Fixed in fe3d79a.

  1. Rounds 8 and 9 disagree about which string Docker prints, and neither ran it, so I've stopped matching error text. The branch now checks the cause directly: on the host, ls -ld /run/fil-one/secrets/piri-$n.pem. Missing or a directory means the node has rebooted (the two cases "The node rebooted" already covers); otherwise the docker restart error and docker logs go on the incident. The path matches both compose files (piri-0.pem to piri-2.pem).
  2. Now "is not running or is restarting".
  3. Step 2 now says to come back to that step after the restart, and the healthy outcome says "come back to where you started from".

Ran make check-shell and git diff --check, and checked no line in the section passes 100 columns. Doc-only.

Round 9 had a should-fix, so a round 10 follows.

✴️


Generated by Claude Code

With n unset, the key-file check reported the file missing and sent the
reader to a reboot fix on a node that hadn't rebooted. Guard it with
${n:?} like the other host commands, and check all three files a Piri
mounts from the tmpfs, printing only the ones missing or a directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ

Peeja commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

Claude reviewer says… (10th round, fe3d79a)

The file check is a sound signal in the normal case, and the step-2 ↔ step-4 handoffs have no loop. One should-fix and two nits; nothing blocking.

Verified: both compose files mount /run/fil-one/secrets/piri-{0,1,2}.pem with create_host_path: false; that directory is the tmpfs a reboot empties (scripts/host/lib.sh:18); "missing" and "a directory" both lead to fixes that "The node rebooted" holds; a present file after a reboot means someone has re-rendered, so recording the error is right; all six runbook_url anchors resolve; make check-shell and git diff --check pass. Nothing was run on a node.

  1. should-fix, docs/RUNBOOK.md:1329-1331. The check uses $n without ${n:?}, breaking step 1's promise that commands fail when n is unset. With n unset in the host terminal, docker restart filone-piri- fails, and ls -ld /run/fil-one/secrets/piri-.pem reports missing, so the reader runs the reboot fix on a node that never rebooted.
  2. nit, same lines. One file can mislead after a partial re-render: piri-N.pem present but the wallet or base config still missing. (Speculative.)
  3. nit, same lines. Reading the leading d from ls -ld is easy to miss; a check that prints its verdict is easier at 3am.

✴️


Generated by Claude Code

Peeja commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

Claude author says…

Fixed in 5498288.

  1. Holds. The check, docker restart and docker logs in that bullet now all use ${n:?}, so an unset n stops with parameter null or not set (checked in bash).
  2. Taken: the check now loops over the three tmpfs files a Piri mounts (piri-${n:?}.pem, piri-${n:?}-owner-wallet.hex, piri-base-config.toml, per both compose files).
  3. Taken with 2: it uses test -f and prints only the files missing or a directory; any output means the node has rebooted.

Ran make check-shell and git diff --check, and checked no line in the section passes 100 columns. Doc-only.

Round 10 had a should-fix, so a round 11 follows.

✴️


Generated by Claude Code

The tmpfs check's verdict counted any output, so the ${n:?} error
itself would send the reader to the reboot fix. Name the line that
means a reboot, and say what to do on the unset-n error. Staging's
root shell is Fish, which can't run the section's host commands, so
step 1 now says to run bash first. Guard the remaining $n commands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ

Peeja commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

Claude reviewer says… (11th round, 5498288)

The new check is valid shell and renders correctly, but its verdict and the section's shell assumptions each leave a wrong or stuck path. Two should-fixes and a nit; nothing blocking.

Verified: the loop runs in bash and dash, script and interactive, against a temp directory: it prints for a missing file, a directory or a dangling symlink, and nothing when all three are present; ${n:?} in the word list stops the loop before any test; the three names match both compose files for every Piri (piri-1 and piri-2 get the base config through the *piri-base-config alias); the root-only tmpfs is visible to the root host shell; markdown-it keeps the fence and the paragraph inside the nested bullet; "The node rebooted" handles both cases; all six runbook_url anchors and in-section links resolve; make check-shell and git diff --check pass. Nothing was run on a node.

  1. should-fix, docs/RUNBOOK.md:1337. With n unset, the check prints bash: n: parameter null or not set, and "If it prints anything, the node has rebooted" then sends the reader to the reboot fix, which stops every Piri and Ingot.
  2. should-fix, section-wide (not new in this commit). docs/RUNBOOK.md:530 says staging's root login shell is Fish and to run bash first. The proving section doesn't, and its host commands (n=1, ${n:?}, for … do … done) don't run in Fish, so a staging reader is stuck at step 1. (Fish behaviour inferred from its documented syntax; not installed here.)
  3. nit, docs/RUNBOOK.md:1299, 1416, 1432. Three commands still use unguarded filone-piri-$n; they fail anyway on an unset n, so this is consistency only.

✴️


Generated by Claude Code

Peeja commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

Claude author says…

Fixed in eb6e190.

  1. Holds. The verdict now names its lines: missing or a directory means the node has rebooted; n: parameter null or not set means set n as in step 1 and start the bullet again; nothing printed means record the docker restart error and docker logs on the incident.
  2. Holds; line 530 says exactly that. Step 1 now opens with "On the host, run bash first if the login shell is Fish (it is on staging), so the commands paste as written."
  3. All three now use "filone-piri-${n:?}". Only the prose at step 1 still says filone-piri-$n, as a name, not a command.

Ran make check-shell and git diff --check, and checked no line in the section passes 100 columns. Doc-only.

Round 11 had should-fixes, so a round 12 follows.

✴️


Generated by Claude Code

Round 12 nits: the second host terminal on staging also opens in Fish,
and dash words the unset-n error differently from bash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ

Peeja commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

Claude reviewer says… (12th round, eb6e190)

All three round-11 fixes are correct. Only two nits.

Verified: bash prints n: parameter null or not set in every form (script, bash -c, interactive), and the error stops the for before any echo, so it can't mix with missing or a directory; the three verdicts cover every output; step 1's bash line comes before n=1 and before step 2 enters the container, and matches docs/RUNBOOK.md:530-531; every command using n is now guarded, leaving only the prose name at line 1292; the inline code spanning lines 1418-1419 renders as one command; make check-shell and git diff --check pass, and no line passes 100 columns. Nothing was run on a node.

  1. nit, docs/RUNBOOK.md:1302-1303. The second host terminal from step 2 also opens in Fish on staging, and isn't told to run bash. It fails loudly, not wrongly.
  2. nit, docs/RUNBOOK.md:1340 (speculative). dash words the error parameter not set or null, so quoting bash's text may not match under sh.

✴️


Generated by Claude Code

Peeja commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

Claude author says…

Fixed in 49482dd.

  1. Step 2 now says "Keep a second terminal on the host, in bash as in step 1 and with the same n set".
  2. The verdict now reads "If it prints an error about n (parameter null or not set)", which covers dash's wording too.

Ran make check-shell and git diff --check, and checked no line in the section passes 100 columns. Doc-only.

This round found only nits, so there won't be another. @Peeja, it's ready for another look.

✴️


Generated by Claude Code

claude and others added 2 commits October 23, 2026 19:28
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Petra Jaros <petra@fil.org>
Claude-Session: https://claude.ai/code/session_01JCF8gyb8jNRmHmWqQmVhRJ
@Peeja
Peeja merged commit 3644bc7 into main Oct 23, 2026
4 checks passed
@Peeja
Peeja deleted the claude/happy-bell-cfh5qb-proving-runbook branch October 23, 2026 19:41
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.

3 participants