A run whose work ended after the skill deadline with an error kept its
earlier status, so a slow PostProcess that failed stored a bad_output row
while the caller received a timeout. Any run past the deadline now stores
provider_error, with the error text still wrapping the underlying error.
Adds a test for the late-failure path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- runOnce no longer saves with an unbounded context.WithoutCancel. Work
that ended in time must write its row before the skill deadline; if the
writer is not free by then the run fails with llm.ErrTimeout and its
row records the timeout. A failed run's row is written within
runSaveTimeout (5s); if that write fails too, the run returns both
errors and no row is stored. A stored row's status now always matches
what the caller received. The row ID is fixed per run, so a second
save attempt can never add a second row.
- skill, provider and model are redacted with the same redactor as the
bodies; TestRunRowsHoldNoSecrets seeds the API key and a webhook URL
into the model value.
- guard_test: "row saved late" now expects a provider_error timeout row;
new "writer blocked" case proves Run returns ErrTimeout within the
bound and stores no row.
- Purge doc and ARCHITECTURE: the daily purge is owned by Feature 20's
scheduler, which must call harness.Purge.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>