Measure Before You Fix
Convention: see conventions/brain-first.md —
before re-deriving a diagnosis, search the brain for prior incidents of
the same alert. A recurring alert usually has a recorded verdict already.
Route here on any alert whose claim is temporal — "X is stale", "step
timed out", "pipeline wedged", "job is slow", "N hours behind". These alerts
invite an immediate structural fix (raise the timeout, split the step,
reorder the pipeline). Do the measurement first. It is almost always cheaper
than the fix, and it frequently invalidates it. (Routing is a harness
convention, not a mechanical guarantee — the contract below is the
discipline that makes it stick.)
On gbrain surfaces this covers: gbrain doctor staleness checks (e.g. sync
freshness, cycle freshness), autopilot cycle alerts, the sync stall watchdog
(reason: 'stall_timeout'), and any cron monitor built on top of them.
The rule
One stopwatch measurement of the suspect step, before any code change.
If you cannot state the measured duration of the thing you claim is slow, you
do not yet know the root cause, and any fix you write is a guess wearing a diff.
Contract
This skill guarantees:
- No structural fix (timeout raise, step split, pipeline reorder, wrapper
rewrite) is proposed before a measured duration of the blamed step exists.
- The measurement targets the specific entity the alert named, not the
aggregate (
--all hides which member is slow).
- The alert's threshold is compared against the authoritative one
(
gbrain doctor's warn/fail lines) before the system is declared unhealthy.
- The verdict explicitly distinguishes "needs more time" from "is wedged" —
they have opposite fixes.
- Read-only: this skill changes no timeouts, thresholds, or code. It produces
a measurement verdict that sizes the fix; the fix itself is a separate,
now-informed change.
Procedure
Read the alert's own numbers. They often contradict the theory already.
(Locks: none means it isn't lock contention. Note it and drop that branch.)
Time the suspect step directly. Isolate the smallest unit that the alert
blames, and run it with a clock:
time gbrain sync --source source-a --no-embed
Run it on the specific named entity, not the aggregate. A whole-brain run
hides which member is slow; --source source-a answers the question.
Cross-check state with gbrain sources status (per-source sync lag).
Compare measured vs. budgeted. Grep every timeout in the wrapper, not
just the default in the helper signature:
grep -n "timeoutMs\|timeout:" <the wrapper or cron script>
A generous per-call override makes the helper's default irrelevant. Check the
call site before blaming the default.
Check the alert threshold against the authoritative one. Before
concluding the system is broken, confirm the alerter and the audit agree on
what "bad" means. gbrain doctor's sync-freshness check defaults to
24h warn / 72h fail (env-overridable via GBRAIN_SYNC_FRESHNESS_WARN_HOURS
/ GBRAIN_SYNC_FRESHNESS_FAIL_HOURS); a cron monitor paging at 12h is
speaking below the authoritative warn line. A monitor that acts early is
correct; a monitor that speaks at its act-line is a false-positive
generator.
Only now design the fix — against the number you measured.
The threshold-mismatch failure mode
A cron monitor legitimately acts earlier than the doctor fails, to keep drift
out of FAIL territory. That is good design. The bug is reusing the
act-threshold as the alert-threshold: everything between "act" and "warn"
becomes a recurring page about a healthy system.
Separate the two constants. Act at the aggressive line, speak at the
authoritative one:
const ACT_HOURS = Number(env.MONITOR_ACT_HOURS || 12); // act early — fine
const ALERT_HOURS = Math.max(ACT_HOURS, DOCTOR_WARN_HOURS); // speak at the audit's line
Symptom to recognize instantly: a repeating alert whose numbers sit below the
doctor's own warn line, while the underlying resource looks fine when queried
directly.
Red flags that you are theorizing, not diagnosing
- You have a root cause but no measured duration.
- Your fix is a rewrite and you have not run the step once.
- You revise the theory twice without taking a new measurement between revisions.
- The alert says "no recovery in flight" — verify whether recovery is in fact
running before believing it (
ps for the worker; check the launch flag;
gbrain jobs list for queued work).
- "Already up to date" in the step output. That step is not your bottleneck.
Anti-Patterns
- Raising a timeout to fix a stall. If the step is genuinely hung, a bigger
budget just hangs longer. Measure, then decide between "needs more time" and
"is wedged" — they have opposite fixes. (gbrain's sync stall watchdog makes
the same distinction natively: it keys on forward progress, not elapsed time.)
- Rewriting a pipeline on an unmeasured starvation theory. Splitting steps to
fix starvation that does not exist adds surface area and fixes nothing.
- Trusting the alert's causal claim. Alerts report symptoms accurately and
causes badly. The staleness number is real; the reason attached to it is a guess.
- Skillifying or persisting a root cause you have not measured. A confident
wrong diagnosis baked into a playbook is worse than the original bug.
Known failure modes handled
- Triple-wrong diagnosis on a freshness alert. A cron monitor paged
repeatedly about two sources (
source-a, source-b) reported hours-stale.
Three successive root causes were asserted and a wrapper rewrite approved —
before any measurement. The measurement:
time gbrain sync --source source-a --no-embed finished in single-digit
seconds with "Already up to date" (same for source-b), and
gbrain sources status showed every source synced that morning. Every
theory died at once. Actual cause: the monitor alerted at its
act-threshold, hours below gbrain doctor's authoritative warn line. The
fix was two lines (ALERT_HOURS = max(ACT_HOURS, WARN_HOURS)), not a
rewrite. Lesson: when a page repeats about a system that measures healthy,
suspect the thresholds before the system.
- Contention-theory corollary caught in the same pass: a CPU-contention
worry about a deprioritized (
nice'd) step was equally unfounded — the step
finished in seconds while the host was under sustained concurrent load.
Contention theories need the same stopwatch as staleness theories.
Output Format
The output is a measurement verdict (conversation-level; this skill writes no
brain pages). Only after the verdict is a fix proposed, sized against the
measured number:
## Measurement verdict
- Alert: <the alert text and which monitor emitted it>
- Claim: <the temporal claim, e.g. "source-a 14h stale">
- Measured: <exact command> → <duration> (<key output, e.g. "Already up to date">)
- Budgeted: <timeout constant + any call-site override, file:line>
- Thresholds: monitor act-line <X>h vs doctor warn-line <Y>h → <match | MISMATCH>
- Verdict: false page on healthy system | needs more time | wedged | genuine regression
- Fix: <the change, justified by the measured number — or "none; adjust the alert line">
Dedup (sharp boundaries)
- GStack
investigate — systematic debugging of code bugs ("why is this
broken", 500 errors, wrong output). Boundary: investigate root-causes code
behavior; this skill is the measure-first gate for temporal ops alerts
(stale/timeout/freshness/wedged) that runs before any timeout or threshold
is touched. If the stopwatch confirms a genuine slowness or regression, hand
off to investigate with the measured number.
skills/maintain/SKILL.md — runs brain health checks and repairs
(doctor, extraction, dream cycle). Boundary: maintain emits and acts on
health output; this skill governs how to respond when one of those checks
pages, before budgets or wrappers change.
- smoke-test (host-side) — binary post-restart health checks with
auto-fix. Boundary: smoke-test answers "is it up after a restart"; this
skill answers "is this slow/stale claim even true".
skills/cron-scheduler/SKILL.md — schedules monitors and jobs.
Boundary: cron-scheduler decides when monitors run; this skill supplies
the act-line vs alert-line rule their thresholds must encode.
skills/conventions/test-before-bulk.md — trial-before-bulk for
mutations. Same spirit (evidence before action), different object: that
convention gates bulk writes; this skill gates timeout/threshold/pipeline
changes.
1---2name: measure-before-you-fix3description: Before fixing a slow/stale/timeout alert, measure the step yourself. Kill the theory with a stopwatch, not a code change. Measure-first ops triage for temporal alerts (stale, timeout, freshness, wedged, N hours behind) from gbrain doctor, autopilot, sync, and cron monitors — runs BEFORE any timeout raise, threshold change, or pipeline rewrite.4---5
6# Measure Before You Fix
7
8> **Convention:** see [conventions/brain-first.md](../conventions/brain-first.md) —
9> before re-deriving a diagnosis, `search` the brain for prior incidents of
10> the same alert. A recurring alert usually has a recorded verdict already.
11
12Route here on any alert whose claim is **temporal** — "X is stale", "step
13timed out", "pipeline wedged", "job is slow", "N hours behind". These alerts
14invite an immediate structural fix (raise the timeout, split the step,
15reorder the pipeline). Do the measurement first. It is almost always cheaper
16than the fix, and it frequently invalidates it. (Routing is a harness
17convention, not a mechanical guarantee — the contract below is the
18discipline that makes it stick.)
19
20On gbrain surfaces this covers: `gbrain doctor` staleness checks (e.g. sync
21freshness, cycle freshness), autopilot cycle alerts, the sync stall watchdog
22(`reason: 'stall_timeout'`), and any cron monitor built on top of them.
23
24## The rule
25
26**One stopwatch measurement of the suspect step, before any code change.**
27
28If you cannot state the measured duration of the thing you claim is slow, you
29do not yet know the root cause, and any fix you write is a guess wearing a diff.
30
31## Contract
32
33This skill guarantees:
34
35- No structural fix (timeout raise, step split, pipeline reorder, wrapper
36 rewrite) is proposed before a measured duration of the blamed step exists.
37- The measurement targets the *specific* entity the alert named, not the
38 aggregate (`--all` hides which member is slow).
39- The alert's threshold is compared against the authoritative one
40 (`gbrain doctor`'s warn/fail lines) before the system is declared unhealthy.
41- The verdict explicitly distinguishes "needs more time" from "is wedged" —
42 they have opposite fixes.
43- Read-only: this skill changes no timeouts, thresholds, or code. It produces
44 a measurement verdict that sizes the fix; the fix itself is a separate,
45 now-informed change.
46
47## Procedure
48
491. **Read the alert's own numbers.** They often contradict the theory already.
50 (`Locks: none` means it isn't lock contention. Note it and drop that branch.)
51
522. **Time the suspect step directly.** Isolate the smallest unit that the alert
53 blames, and run it with a clock:
54
55 ```bash
56 time gbrain sync --source source-a --no-embed
57 ```
58
59 Run it on the *specific* named entity, not the aggregate. A whole-brain run
60 hides which member is slow; `--source source-a` answers the question.
61 Cross-check state with `gbrain sources status` (per-source sync lag).
62
633. **Compare measured vs. budgeted.** Grep every timeout in the wrapper, not
64 just the default in the helper signature:
65
66 ```bash
67 grep -n "timeoutMs\|timeout:" <the wrapper or cron script>
68 ```
69
70 A generous per-call override makes the helper's default irrelevant. Check the
71 call site before blaming the default.
72
734. **Check the alert threshold against the authoritative one.** Before
74 concluding the system is broken, confirm the alerter and the audit agree on
75 what "bad" means. `gbrain doctor`'s sync-freshness check defaults to
76 24h warn / 72h fail (env-overridable via `GBRAIN_SYNC_FRESHNESS_WARN_HOURS`
77 / `GBRAIN_SYNC_FRESHNESS_FAIL_HOURS`); a cron monitor paging at 12h is
78 speaking below the authoritative warn line. A monitor that *acts* early is
79 correct; a monitor that *speaks* at its act-line is a false-positive
80 generator.
81
825. **Only now design the fix** — against the number you measured.
83
84## The threshold-mismatch failure mode
85
86A cron monitor legitimately acts earlier than the doctor fails, to keep drift
87out of FAIL territory. That is good design. The bug is reusing the
88act-threshold as the alert-threshold: everything between "act" and "warn"
89becomes a recurring page about a healthy system.
90
91**Separate the two constants.** Act at the aggressive line, speak at the
92authoritative one:
93
94```js
95const ACT_HOURS = Number(env.MONITOR_ACT_HOURS || 12); // act early — fine
96const ALERT_HOURS = Math.max(ACT_HOURS, DOCTOR_WARN_HOURS); // speak at the audit's line
97```
98
99Symptom to recognize instantly: **a repeating alert whose numbers sit below the
100doctor's own warn line**, while the underlying resource looks fine when queried
101directly.
102
103## Red flags that you are theorizing, not diagnosing
104
105- You have a root cause but no measured duration.
106- Your fix is a rewrite and you have not run the step once.
107- You revise the theory twice without taking a new measurement between revisions.
108- The alert says "no recovery in flight" — verify whether recovery is in fact
109 running before believing it (`ps` for the worker; check the launch flag;
110 `gbrain jobs list` for queued work).
111- "Already up to date" in the step output. That step is not your bottleneck.
112
113## Anti-Patterns
114
115- **Raising a timeout to fix a stall.** If the step is genuinely hung, a bigger
116 budget just hangs longer. Measure, then decide between "needs more time" and
117 "is wedged" — they have opposite fixes. (gbrain's sync stall watchdog makes
118 the same distinction natively: it keys on forward progress, not elapsed time.)
119- **Rewriting a pipeline on an unmeasured starvation theory.** Splitting steps to
120 fix starvation that does not exist adds surface area and fixes nothing.
121- **Trusting the alert's causal claim.** Alerts report symptoms accurately and
122 causes badly. The staleness number is real; the reason attached to it is a guess.
123- **Skillifying or persisting a root cause you have not measured.** A confident
124 wrong diagnosis baked into a playbook is worse than the original bug.
125
126## Known failure modes handled
127
128- **Triple-wrong diagnosis on a freshness alert.** A cron monitor paged
129 repeatedly about two sources (`source-a`, `source-b`) reported hours-stale.
130 Three successive root causes were asserted and a wrapper rewrite approved —
131 before any measurement. The measurement:
132 `time gbrain sync --source source-a --no-embed` finished in single-digit
133 seconds with "Already up to date" (same for `source-b`), and
134 `gbrain sources status` showed every source synced that morning. Every
135 theory died at once. Actual cause: the monitor alerted at its
136 act-threshold, hours below `gbrain doctor`'s authoritative warn line. The
137 fix was two lines (`ALERT_HOURS = max(ACT_HOURS, WARN_HOURS)`), not a
138 rewrite. Lesson: when a page repeats about a system that measures healthy,
139 suspect the thresholds before the system.
140- **Contention-theory corollary caught in the same pass:** a CPU-contention
141 worry about a deprioritized (`nice`'d) step was equally unfounded — the step
142 finished in seconds while the host was under sustained concurrent load.
143 Contention theories need the same stopwatch as staleness theories.
144
145## Output Format
146
147The output is a measurement verdict (conversation-level; this skill writes no
148brain pages). Only after the verdict is a fix proposed, sized against the
149measured number:
150
151```
152## Measurement verdict
153- Alert: <the alert text and which monitor emitted it>
154- Claim: <the temporal claim, e.g. "source-a 14h stale">
155- Measured: <exact command> → <duration> (<key output, e.g. "Already up to date">)
156- Budgeted: <timeout constant + any call-site override, file:line>
157- Thresholds: monitor act-line <X>h vs doctor warn-line <Y>h → <match | MISMATCH>
158- Verdict: false page on healthy system | needs more time | wedged | genuine regression
159- Fix: <the change, justified by the measured number — or "none; adjust the alert line">
160```
161
162## Dedup (sharp boundaries)
163
164- **GStack `investigate`** — systematic debugging of code bugs ("why is this
165 broken", 500 errors, wrong output). Boundary: `investigate` root-causes code
166 *behavior*; this skill is the measure-first gate for temporal *ops alerts*
167 (stale/timeout/freshness/wedged) that runs before any timeout or threshold
168 is touched. If the stopwatch confirms a genuine slowness or regression, hand
169 off to `investigate` with the measured number.
170- **`skills/maintain/SKILL.md`** — runs brain health checks and repairs
171 (doctor, extraction, dream cycle). Boundary: maintain *emits and acts on*
172 health output; this skill governs how to respond when one of those checks
173 pages, before budgets or wrappers change.
174- **smoke-test (host-side)** — binary post-restart health checks with
175 auto-fix. Boundary: smoke-test answers "is it up after a restart"; this
176 skill answers "is this slow/stale claim even true".
177- **`skills/cron-scheduler/SKILL.md`** — schedules monitors and jobs.
178 Boundary: cron-scheduler decides *when* monitors run; this skill supplies
179 the act-line vs alert-line rule their thresholds must encode.
180- **`skills/conventions/test-before-bulk.md`** — trial-before-bulk for
181 mutations. Same spirit (evidence before action), different object: that
182 convention gates bulk *writes*; this skill gates timeout/threshold/pipeline
183 *changes*.