th-fc07ac: the workflow step-attempt cap never fired — shadowed binding fed it 0 - #368
Merged
Conversation
…ng fed it 0 handler.rs carried the cap capture block twice, back to back, differing in one field: the first took the count from durable conversation metadata, the second from the per-pod session map. Rust shadowing meant the second won, silently reverting th-c12df5. Worse, the two stores used different metadata keys — persist_workflow_step writes `workflowStepAttempts`, the session accessor read `stepAttempts`, which nothing ever wrote, and its writer had no callers at all. So the cap was fed a permanent 0 and a step the judge never accepts could loop forever (the th-d57a1d case the cap exists to bound). Keeps the durable source, deletes the duplicate, and removes the two vestigial AppState accessors so the wrong source is unavailable rather than merely un-used. Clears three unused-variable warnings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YbN45JeWDbcjvFqGJvmVD3
🦋 Changeset detectedLatest commit: 09354f9 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
handler.rscarried the step-attempt-cap capture block twice, back to back, with identical comments. The two copies differed in exactly one field:Rust shadowing means the second won. That silently reverted th-c12df5, which had deliberately moved the workflow pointer and its attempt count onto durable conversation storage — the comment ten lines above the duplicate still explains why: the per-pod map "reset them to step 0 every turn, freezing the workflow at the first step so the judge/cap could never advance it".
It was worse than the pre-th-c12df5 behaviour. The two stores use different metadata keys:
persist_workflow_stepwritesworkflowStepAttempts, while the session accessor readstepAttempts— a key nothing ever writes. Its writer,set_session_step_attempts, had no callers anywhere in the repo. Soapply_step_capwas fed a permanent 0,next_attemptsnever reachedWORKFLOW_STEP_ATTEMPT_CAP, and a workflow step the judge never accepts could loop forever — precisely the pathological-visitor case (th-d57a1d) the cap exists to bound.Which source is correct
The durable one, unambiguously. Not a judgement call:
max(loaded, live)reconciliation is warranted) — it is never written at all, and reads a key no writer produces.The fix
Keep
loaded_attempts, delete the duplicate block — and removeAppState::session_step_attempts/set_session_step_attemptsrather than leave them. Both were vestigial from the pre-th-c12df5 design, neither had a live caller, and the getter's doc comment still advertised itself as feeding the cap. Leaving them in place is what let the wrong source get wired in; removing them makes the mistake unavailable rather than merely un-made. Also clears threeunused variablewarnings emitted on every build.Test
step_attempt_cap_counts_across_reconnects_and_force_advancesdrives the real per-turn pipeline —load_workflow_step→apply_step_cap→persist_workflow_step— reloading from storage each turn as a reconnect or pod hop would, with a judge that always holds, and asserts the step force-advances exactly at the cap with the counter reset.Verified it has teeth by mutation: replacing the loaded count with a hardcoded
0(the bug) fails it witha step the judge never advances must force-advance on the capth turn. Reverted before committing.The existing
workflow_step_persists_to_conversation_metadata_and_resumesalready covered the storage round-trip, so this adds the composition case it did not.Verification
cargo fmt --all -- --checkclean;cargo test --workspace647 passed, 0 failed, and zerounused variablewarnings. The dynamodb suite passed too (fixed on main by #366, so the rust lane is trustworthy again). anchor-guard passes naming@smooai/smooth-operator(stamped trees touched:rust,rust/smooth-operator-server).Changeset is
minorrather thanpatch— this removes twopubmethods fromAppState.Found while triaging PR #333 under th-b093c7.
🤖 Generated with Claude Code
https://claude.ai/code/session_01YbN45JeWDbcjvFqGJvmVD3