What happened?
When a boundary raises during a live recording, _record_failure() correctly stores error / error_type / finish_reason="error" on the envelope. But envelope_to_return_value() (used by session.stub_result(), and by chronicle.wrap(client)'s _stub()) never checks action_result.error. For a kind="tool" boundary it falls through to {"status": envelope.action_result.completion or "ok", "blocked": False} — since completion is never set on a failure, that's literally {"status": "ok", "blocked": False}.
So replaying a fixture that recorded a crash returns a fabricated success value instead of re-raising the original exception. This defeats the core "reproduce a production failure as a committed regression test" promise for the one case where the incident is a crash — e.g. a tool call that raised in prod has no way to be reproduced as a failing regression test today.
Separately, chronicle.wrap(client)'s live-mode wrapper doesn't even catch exceptions from create(*args, **kwargs) — so an OpenAI/Anthropic client call that raises is never recorded as a failure envelope in the first place, which compounds the same gap for that entry point.
Expected: replaying an envelope with a recorded error re-raises (something like ReplayedError(boundary_id, error_type, message)), and wrap(client) records failures the same way @boundary already does.
Reproduction
import chronicle
from chronicle import boundary
@boundary("flaky", kind="tool")
def flaky(x: int) -> dict:
raise ValueError("boom")
with chronicle.record("incident", export="fixtures/traces/incident/"):
try:
flaky(1)
except ValueError:
pass
with chronicle.replay_trace("fixtures/traces/incident/") as session:
result = flaky(1) # expected: re-raises ValueError; actual: returns {"status": "ok", "blocked": False}
Chronicle version
0.3.0
Python version
3.13
What happened?
When a boundary raises during a live recording, _record_failure() correctly stores error / error_type / finish_reason="error" on the envelope. But envelope_to_return_value() (used by session.stub_result(), and by chronicle.wrap(client)'s _stub()) never checks action_result.error. For a kind="tool" boundary it falls through to {"status": envelope.action_result.completion or "ok", "blocked": False} — since completion is never set on a failure, that's literally {"status": "ok", "blocked": False}.
So replaying a fixture that recorded a crash returns a fabricated success value instead of re-raising the original exception. This defeats the core "reproduce a production failure as a committed regression test" promise for the one case where the incident is a crash — e.g. a tool call that raised in prod has no way to be reproduced as a failing regression test today.
Separately, chronicle.wrap(client)'s live-mode wrapper doesn't even catch exceptions from create(*args, **kwargs) — so an OpenAI/Anthropic client call that raises is never recorded as a failure envelope in the first place, which compounds the same gap for that entry point.
Expected: replaying an envelope with a recorded error re-raises (something like ReplayedError(boundary_id, error_type, message)), and wrap(client) records failures the same way @boundary already does.
Reproduction
import chronicle
from chronicle import boundary
@boundary("flaky", kind="tool")
def flaky(x: int) -> dict:
raise ValueError("boom")
with chronicle.record("incident", export="fixtures/traces/incident/"):
try:
flaky(1)
except ValueError:
pass
with chronicle.replay_trace("fixtures/traces/incident/") as session:
result = flaky(1) # expected: re-raises ValueError; actual: returns {"status": "ok", "blocked": False}
Chronicle version
0.3.0
Python version
3.13