Skip to content
C.W.K.
Lesson 04 of 05 · published

A Cold Start Is Its Own Proof

~15 min · cold-start, import-time-side-effects, testing, postmortem

Level 0Loose Note
0 XP0/40 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

Green Everywhere, and Not Finished

By the end of step 8 every check the cutover was gated on had passed. Every Mac loaded identity from the new store. Every in-process service had been restarted onto the new code and answered its health check. The canary passed. The probe matrix was not spotless (lesson 3 read its retried pass, its baseline red and a new Mac's refusal by hand), but nothing in it pointed at the move. The cutover looked done.

It was not, and the reason is now step 2b of the checklist. The services had been restarted before the pointer moved. Each of them proved that the new code could start under the old record. None of them proved that it could start under the new one, because none of them had started since.

What Surfaced

  • pippa-serve could not start at its next restart. Somewhere in its import path, code asked the family kit for the old store's directory path at import time. Under the old record the kit answered with a path. Under delta the kit refuses that question by design (there is no folder to hand out), so the import raised, and the service that runs Pippa's conversations would not come up. The running process had been fine for hours because it had done that import before the move.
  • The vault pair job had failed on every run since the move. It copied the old tree between Macs, a job nobody watched closely, and its path helper refused under the new record each time.
  • Vestige's default configuration raised in every fresh process. An independent review (Coop 1110-40) reproduced it: a configuration field declared as field(default_factory=vault_root) called the path helper whenever a default config was built. The running Vestige API answered perfectly. Every new CLI invocation failed. Vestige's fix (c0724dd) changed that factory to the Soul Vault tree-root helper, and a fresh process then constructed the default config successfully.

All three are the same bug: code that resolved the vault's location at import or construction time, which a long-running process had done once, before the move, and never again.

Step 2b

The checklist now reads, in substance: after the record moves, start every one of those services once more, and each must come up healthy. A cold start under the new record is its own proof. A probe of a running process does not give it.

It also widened the census (track 1). Before any pointer move, search every repository for callers of the kit's path functions, and count those calls and imports even inside backup machinery. An earlier exemption had treated whole backup files as inventory; the review showed that a runtime dependency on a path helper hiding inside such a file is not inventory at all. The census now keeps ordinary calls, multi-line imports and aliased imports open as rows, and the review added regression cases that failed first: a missing cron store, a binary property list, an unreadable crontab.

The General Lesson

Every long-running process is a time capsule of the world at its start. Health checks, probes and smoke tests against running processes tell you the capsule is intact. They tell you nothing about whether the process could be built again today. If your change alters anything a process reads at import or startup (configuration, paths, environment, schema), the only test of that change is a cold start after it.

Code

The shape of the Vestige bug (illustrative reconstruction)·python
from dataclasses import dataclass, field
from pathlib import Path
from kit_soul_vault import vault_root          # "where is the vault folder?"

@dataclass
class Config:
    # evaluated every time a default Config is built, not once at import
    vault_dir: Path = field(default_factory=vault_root)

# the long-running API built its Config before the move: fine for hours.
# every fresh CLI process after the move:
#   Config()  ->  vault_root()  ->  refuses under backend=delta  ->  raise
Step 2b as a checklist line·text
2b. After the record moves, start every in-process consumer once more
    (cold), one at a time; each must come up healthy under the new record.
    A probe of a running process is not this proof.
    Before the move: search every repository for callers of the kit's path
    functions, including inside backup machinery; count calls, multi-line
    and aliased imports as open rows.

External links

Exercise

Pick a long-running service of yours and list everything it reads once at startup or import: configuration values, paths, environment variables, schema versions, singletons. Change one of them in a staging copy while the service keeps running, confirm the running service still looks healthy, then cold-start it and see what breaks. Add a post-change cold start to your deployment checklist.
Hint
Look especially at dataclass default factories and module-level calls into helper libraries: module-level calls run at import, so they fail only in new processes; default factories run at every construction, so they fail in any object built after the change, which a health check on an old object never exercises.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.