The SessionStart debug log held 719 start: payload lines since the earlier fix went in, and not one of them carried agent_id. The guard that fix added never fires.
I had been asked to check whether the fix was complete. The log records keys only, and the resume payloads carried just these:
No agent_id anywhere in that list, so the guard was written against a field that never arrives.
Did the resets still happen?
They did. The stale-resume reset had logged 8 times since the fix landed on 10-03. I checked four of those from 10-11, and all four had source=compact. Each had a heartbeat more than 30 minutes old, and its prior_tasks and prior_area showed the wipe had happened.
A compaction is the same chat continuing. A long unattended run leaves the heartbeat stale, and the reset then cleared the classification.
I also searched the manifests for prior_* set while area: unk. There was exactly one match, and it came from a /resync stale marking, not from this reset. Its tasks were never cleared.
What I changed
The stale-heartbeat reset in cmd_start now skips source == "compact". Stale resets on source=resume are unchanged. They can’t be told apart from a reused sid, so I left them.
The wider tests, python -m pytest -q tests -k "session_hook or footer", passed 194. The split-gate check came back red, but the remaining failure came from another session’s uncommitted edit to a different file, not from my change. I committed only my two files and the fix is on origin/main.
What the first fix got wrong
The first fix keyed its guard on agent_id, a field that none of the 719 start payloads carried, and the stale-resume reset logged 8 times after it landed. The new skip keys on source, which every resume payload in the log does carry, and the four compact resets I read are the case it covers.
AI Skills
Use this lesson with the AI assistant you already use
A guard added to stop session resets keyed on an `agent_id` field, and the debug log held 719 SessionStart start payloads since the fix with none carrying that field. The stale-resume reset still logged 8 times after the fix landed.
Paste the prompt, share only the context needed to answer it, and treat the result as a draft for your review. Do not include confidential information or let an AI assistant make changes without your approval.
The prompt works in any AI assistant as it is. We are also building a /dxdev skill for visual reports and saved memory.
It is not released yet: see what you can use today.
dxdev LESSON · paste into your AI coding agent
LESSON: Verify A Fix Against The Raw Events It Claims To Change, Not Against The Field Its Code Assumes
SOURCE: dxdev.com/blog/a-fix-is-not-done-until-the-raw-events-agree
WHAT HAPPENED: The author was asked to check whether an earlier fix was complete. The debug log records payload keys only, and the resume payloads listed fields like `source`, `session_id`, and `transcript_path` but never `agent_id`, so the guard was written against a field that never arrives. The author then confirmed the resets were still happening by reading four of the eight logged resets from 10-11. All four had `source=compact` and a heartbeat more than 30 minutes old, and their `prior_tasks` and `prior_area` showed the classification had been wiped. A search of the manifests for `prior_*` set with `area: unk` found one match, which came from a `/resync` marking and not from this reset. The new fix skips the stale-heartbeat reset when `source == "compact"`, a field that every resume payload in the log does carry, and leaves stale resets on `source=resume` unchanged because they cannot be told apart from a reused sid.
THE RULE: Before trusting a fix to a hook or event handler, check the real logged payloads for the exact field the fix depends on, and confirm from the raw events that the behavior it targets has stopped. A fix keyed on a field no event contains is a fix that never runs.
CHECK MY CODE, then report PASS or FAIL with file:line for each:
1. For every field a guard or condition reads from an event payload, there is a logged real payload (or a count over the log) showing that field is actually present, not just a schema or assumption that it exists.
2. After a fix ships, the log is searched for post-fix occurrences of the exact event the fix was meant to prevent, and the count is zero or each remaining case is explained.
3. When a fix is judged complete, at least a sample of the remaining raw events (such as the four compact resets read here) is inspected by hand to confirm the fix's condition covers them.
THEN PRINT: a table (check, PASS/FAIL, evidence, fix) + a verdict (applies / partially / OUT_OF_SCOPE / no) + the single most important next action.