Skip to content

fix(workflows): reject a non-integer current_step_index in RunState.load() - #4325

Open
Noor-ul-ain001 wants to merge 1 commit into
github:mainfrom
Noor-ul-ain001:fix/runstate-current-step-index
Open

fix(workflows): reject a non-integer current_step_index in RunState.load()#4325
Noor-ul-ain001 wants to merge 1 commit into
github:mainfrom
Noor-ul-ain001:fix/runstate-current-step-index

Conversation

@Noor-ul-ain001

Copy link
Copy Markdown
Contributor

Summary

  • RunState.load() shape-checks every other persisted field it restores on resume — workflow_id, installed_workflow_id, installed_registry_root, inputs — raising a clean Invalid run state: ... ValueError on a malformed value. current_step_index was the one field passed through with no check at all (state_data.get("current_step_index", 0)).
  • resume() later slices definition.steps[state.current_step_index :] with no guard of its own, so a non-int current_step_index (e.g. a hand-edited or externally-written state.json) reaches that slice and raises a raw TypeError: slice indices must be integers or None or have an __index__ method from deep inside resume(), instead of the same clean domain error every sibling field already gets. A negative value slices from the end of the step list instead of failing, silently resuming from the wrong step.
  • This is the same "validate cleanly vs. crash at runtime on the same bad value" shape already fixed repeatedly for step configs in this codebase (e.g. fix(workflows): require a cases block on switch steps #4144 for switch's cases, fix(workflows): reject mismatched run state IDs #3899 for run-state IDs) — here it shows up in RunState.load()'s field validation instead of a step's validate()/execute() pair.

Test plan

  • Added test_load_rejects_invalid_current_step_index (parametrized over a string, float, negative int, list, dict, and bool) to tests/test_workflows.py::TestRunState
  • Verified the new test fails without the fix — DID NOT RAISE <class 'ValueError'> for all 6 cases — and passes with it (stashed only src/specify_cli/workflows/engine.py, kept the test)
  • Ran tests/test_workflows.py::TestRunState — 31 passed, no regressions
  • Ran the full tests/test_workflows.py — 851 passed; the 17 failed / 82 errored are pre-existing Windows-only symlink-guard tests (need Developer Mode elevation) and PermissionError: [WinError 5] on the shared pytest-of-E tmp dir, both unrelated to this change (confirmed identical on main)

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01FW9fAYsCBCAgdKWovtSyqt

@Noor-ul-ain001
Noor-ul-ain001 requested a review from mnriem as a code owner August 25, 2026 15:26
…oad()

RunState.load() shape-checks every other persisted field on resume --
workflow_id, installed_workflow_id, installed_registry_root, and inputs --
raising a clean "Invalid run state: ..." ValueError on a malformed value.
current_step_index was the one field passed through unchecked. resume()
later slices `definition.steps[state.current_step_index :]` with no guard
of its own, so a non-int value (e.g. a hand-edited or externally-written
state.json) reaches that slice and raises a raw
`TypeError: slice indices must be integers or None or have an __index__
method` from deep inside resume() instead. A negative value slices from
the end instead of failing, silently resuming from the wrong step.

This mirrors the sibling field-validation pattern in RunState.load()
(e.g. the workflow_id/installed_workflow_id checks) and the recurring
"validate cleanly vs. crash at runtime" bug class already fixed across
this codebase for step configs (e.g. github#4144, github#3899).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FW9fAYsCBCAgdKWovtSyqt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant