Playbook › Playbook
A broker export older than a correctly-logged trade reports as drift, and --write would erase the trade
Claim
The drift table is a comparison against a snapshot with a date on it. A row can drift because the file is wrong, or because the export is old — and the tool cannot tell you which.
What happened
/reconcile on 2026-09-24, against exports generated 2026-09-23 16:04:
=== ACCOUNT - N positions in export, 1 drifting ===
tkr tracked qty @ cost | actual qty @ cost what
TICKER Q_tot P_blend | Q_old P_old qty + cost
Read at face value this is the exact signature of the failure [[pitfall-average-cost-derived-not-copied]] was written about: a round share quantity missing, and an average cost that moved — the tell of a specific-lot sale logged sloppily.
It was the opposite. Transactions.md held:
DATE | ACCOUNT | BUY | TICKER | Q_new | P_new | new capital, not existing cash
The arithmetic closes exactly:
Q_old @ P_old + Q_new @ P_new = Q_tot @ P_blended (snapshot agreed to the cent)
A portfolio-specific passage was removed from the public build.
Why this is dangerous and not merely noisy
- The failure signature is identical to the real bug. Quantity gap plus a moved average is precisely what an unlogged specific-lot sale looks like. The 2026-09-23 reconciliation found many such rows, so the prior is strongly toward "believe the drift".
- The printed remedy is destructive.
intake.pyandsync_positions.pyboth print the--writecommand directly beneath the drift table.--writetreats the broker as authoritative — correctly, in general — and would have rewritten the position back toQ_old @ P_old, erasing a real purchase from the position file while leaving it inTransactions.md. The nextledger.py checkwould then report drift caused by the fix. - It inverts the usual direction of error. The whole reconciliation discipline is built on "the broker is right, the file is derived". This is the one case where the file is ahead of the broker, and the discipline points the wrong way.
The rule
- Read the export's as-of date before reading the drift table.
intake.pyprints it in the first column. Any trade inTransactions.mddated after it is expected to drift. - Before believing a drift row, grep the ledger for that ticker. If a logged trade explains the gap arithmetically — shares and weighted-average cost — the file is right and the export is old. Close the arithmetic; do not eyeball the share count.
- A drift row where
tracked > actualis a buy-after-export until proven otherwise. The unlogged-sale case leavestracked > actualtoo, so this is not decisive — but it is the cheaper hypothesis to test first, and testing it costs one grep. - Never
--writeon the same day a trade was logged without re-downloading the export. The fix for a stale export is a fresh export, not a write. - This is the mirror of the CLAUDE.md sequencing trap. That one warns a trade dated after the baseline double-counts; this one warns a trade dated after the export reads as missing. Both are the same root cause: three artifacts with three different as-of dates, and only one of them prints its date.
What would falsify this
A sync_positions.py that parses the export's as-of date and excludes ledger trades postdating it
from the comparison — at which point the row would not appear at all. That is the right fix and it
is not built. Until it is, the check is manual.
Related
[[pitfall-average-cost-derived-not-copied]] is the failure this one impersonates; reading them together is the point, because the second is the false positive of the first. [[pitfall-drift-report-read-as-inventory]] — same family: a generated report describes a comparison, not reality, and the comparison has assumptions the report does not restate.
History
- 2026-09-23 — first reconciliation; 24 genuine drifting positions found,
--writecorrectly applied. This established the prior that a drift row means a bookkeeping error. - 2026-09-24 —
/reconcilere-run against the same 9/23 exports after a 9/24 buy. One drift row, entirely spurious. Caught by the dry-run-first discipline in the skill; note written. Nothing was written, no re-anchor was taken.