fix(checks): last_interaction_index wrongly attributes results when a step adds zero new interactions 🤖🤖🤖🤖 - #2604
Conversation
… step adds zero new interactions
ScenarioRunner._run_once computed each step's last_interaction_index as
`len(trace.interactions) - 1 if trace.interactions else None` -- i.e.
None only when the WHOLE cumulative trace is empty, not when THIS
step's interacts added zero new interactions. A step whose interaction
spec yields nothing (e.g. a conditional interaction generator that
sometimes produces no interaction) after an earlier step already
populated the trace gets wrongly attributed to the prior step's last
interaction instead of None, contradicting the field's own documented
contract ("None when the step added no interactions").
TestCaseResult.last_interaction_index's docstring explicitly documents
consumers (e.g. the Giskard Hub upload flow) using this field to
attribute check results to a specific interaction -- a wrong index
here means a check result gets attributed to the wrong interaction in
that downstream flow.
Fix: track interactions_before = len(trace.interactions) before
running the step's interacts, then compare against the count after --
only report an index when interactions actually grew for this step.
Added test_last_interaction_index_is_none_when_step_adds_no_new_interactions,
using an interaction spec that generates zero interactions after an
earlier step already added one. TDD red->green verified. Full
libs/giskard-checks/tests/ suite (739 passed, 4 skipped); ruff/
ruff-format clean; basedpyright 0 errors (pre-existing unrelated
warnings elsewhere in the file untouched).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request fixes an issue in the scenario runner where last_interaction_index could incorrectly point to a prior step's interaction if the current step added no new interactions. The fix compares the interaction count before and after the step is executed, setting the index to None if no new interactions were added. A corresponding unit test has been added to verify this behavior. There are no review comments, and I have no additional feedback to provide.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
Thanks for your contribution. While the fix is correct, it would break uploading to the Hub the results using the Hub SDK I'll take a look more in depth to fix the Hub before merging this fix |
|
Thanks for looking into it, and for flagging the Hub SDK coupling — that's exactly the kind of downstream impact I couldn't see from outside the repo. Happy to defer to your timeline on the Hub fix. If it would help to land this without waiting, I'm also glad to adjust the PR to preserve the old |
|
Thanks @kevinmessiaen — that makes sense, and I appreciate you checking the Hub SDK side before merging. Happy to reshape the fix if it'd make the Hub-side change cleaner on your end (e.g. keeping the old attribution reachable behind a flag), just let me know what fits best. No rush from my side. |
|
Thanks for checking the downstream side — that's exactly the impact I had no way to see from here. One fact that might narrow the scope of the Hub work, since it changes the framing from "this PR introduces
That's the shape So if the Hub upload can't handle No rush from my side at all — happy for this to sit until the Hub is ready, and happy to close it if you'd rather fix both sides together in one go. Just flagging the above in case it makes the Hub change smaller than expected. |
Problem
ScenarioRunner._run_oncecomputed each step'slast_interaction_indexaslen(trace.interactions) - 1 if trace.interactions else None— i.e.Noneonly when the whole cumulative trace is empty, not when this step'sinteractsadded zero new interactions. A step whose interaction spec yields nothing (e.g. a conditional interaction generator that sometimes produces no interaction) after an earlier step already populated the trace gets wrongly attributed to the prior step's last interaction instead ofNone, contradicting the field's own documented contract ("Nonewhen the step added no interactions").TestCaseResult.last_interaction_index's docstring explicitly documents consumers (e.g. the Giskard Hub upload flow) using this field to attribute check results to a specific interaction — a wrong index here means a check result gets attributed to the wrong interaction in that downstream flow.Fix
Track
interactions_before = len(trace.interactions)before running the step'sinteracts, then compare against the count after — only report an index when interactions actually grew for this step.Testing
test_last_interaction_index_is_none_when_step_adds_no_new_interactions, using an interaction spec that generates zero interactions after an earlier step already added one, asserting the second step correctly reportsNone(not the first step's index).runner.pyreproducesassert 0 is None(wrongly attributed to the prior step); reapplying passes.libs/giskard-checks/tests/suite: 739 passed, 4 skipped (unrelated).ruff check/ruff format --checkandbasedpyright— 0 errors (pre-existing unrelated warnings elsewhere in the file untouched).pre-commit run --files— all hooks pass.Note on overlap: #2599 ("fix(checks): record input generation errors") also touches this function and adds a new except-block that reproduces the same unfixed
len(trace.interactions) - 1 if trace.interactions else Nonepattern in a different code path (input-generation-failure handling). This PR doesn't touch that new block, so it's not a duplicate, but merging both may need a small rebase depending on order — happy to reconcile once I know which lands first.AI-Generated disclosure
Found via an AI-assisted code review pass (Claude Code) over
giskard-checks/src/giskard/checks/scenarios/. I personally traced the per-step vs whole-trace distinction against the field's documented contract, reproduced the wrong attribution, verified the fix, and ran the full test suite plus lint/type checks before submitting.