Skip to content

README says agent cannot skip ahead, but since #223 back-to-back ack.sh calls ack never-shown items (0.2.1) #225

Open

Description

@tupe12334

Summary

README says (line 52, under the flow diagram):

Each checklist item requires one full hook invocation to acknowledge. The agent cannot skip ahead.

Since #223 (merged 2026-09-29, shipped in steplock 0.2.1 on crates.io) that is no longer true: ack.sh can be run back to back, so an agent can acknowledge items 2..N without the hook ever showing it those questions, and the gated action is then approved.

#223 made this deliberate (to save ~20 retried tool calls on a 9-step checklist), so this is not a code bug report — it's a docs/behavior contradiction that needs a maintainer decision.

Reproduction (published steplock 0.2.1 via cargo install steplock --locked, macOS arm64)

.steplock/checklists/push-gate/flow.mmd:

stateDiagram-v2
    [*] --> tests
    tests --> docs
    docs --> [*]
    tests : Did you run the tests?
    docs  : Did you update the docs?
Loading

config.toml: on_event = "tool:before", on_tool = "bash", match_input on git + push command words, reset = "session".

# 1. hook fires on a Claude Code PreToolUse Bash event for `git push origin main`
$ steplock < push-event.json
{"hookSpecificOutput":{..."permissionDecision":"deny","permissionDecisionReason":"[push-gate: 1/2] Did you run the tests?\n\nWhen finished, run: sh .../sessions/sess2/push-gate/ack.sh ..."}}

# 2. agent runs ack.sh twice without retrying the command in between
$ sh .steplock/sessions/sess2/push-gate/ack.sh   # acks "tests"
$ sh .steplock/sessions/sess2/push-gate/ack.sh   # acks "docs" — never shown to the agent

# 3. next hook invocation
$ steplock < push-event.json
{}            # approved

Observed vs documented

README 0.2.1 behavior
Hook invocations needed for a 2-item checklist 3 (block, block, approve) 2 (block, approve)
Can the agent ack an item whose question it was never shown? No ("cannot skip ahead") Yes

Everything else checked out on 0.2.1: init, validate, block with the current question, state persisted to state.json, next item on the following invocation when acked one at a time, approve once all acked, branching flow lists both options and follows the chosen one (no-arg / bogus choice rejected with exit 1), audit.log entries written.

Decision needed

Either:

  1. Keep back-to-back acks (fix(ack): set the next state's transitions when ack.sh advances #223's intent) → update the README sentence (e.g. "items must be acknowledged in order; ack.sh advances one state per call and may be run repeatedly"), or
  2. Restore one-question-per-invocation → make ack.sh refuse to advance past a state the gate hasn't displayed yet (e.g. a shown marker the gate sets on block and ack.sh requires).

Not opening a PR since the right fix depends on which guarantee you want. @tupe12334


Filed automatically by the moadim routine "Nightly lib dogfood → issue + PR (polyhook, block-no-verify, steplock)".

Activity

  1. tupe12334 commented on Oct 27, 2026

    @tupe12334
    MemberAuthor

    Still reproduces on the current published release, steplock 0.2.2 (cargo install steplock, macOS arm64, Rust 1.97.1). The README sentence is unchanged (README.md line 54: "Each checklist item requires one full hook invocation to acknowledge. The agent cannot skip ahead.").

    Repro, 2-item checklist ([*] --> a --> b --> [*], reset = "session", gate on bash + git push), fresh session id sess3:

    $ steplock < push-event.json
    steplock: block [push-gate] state=a session=sess3
    {"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny",
      "permissionDecisionReason":"[push-gate: 1/2] Did you run the tests?\n\nWhen finished, run: sh .../sessions/sess3/push-gate/ack.sh\nThen retry your original command."}}
    
    $ sh .steplock/sessions/sess3/push-gate/ack.sh   # acks a
    $ sh .steplock/sessions/sess3/push-gate/ack.sh   # acks b — its question was never shown
    
    $ steplock < push-event.json
    {}      # approved after a single block

    So a 2-item checklist still clears in 2 hook invocations instead of 3, and item b ("Did you update the docs?") is never presented. The enabling line is still core/scripts/ack.sh:

    .transitions = (.flow_transitions[$next] // []) |

    which repopulates the valid transitions for the state ack.sh just advanced to, so the next ack.sh call succeeds with no gate invocation in between.

    For whoever picks this up, option 2 from the original report looks like a small change: ack.sh would clear transitions/next_state after advancing (the gate recomputes both from flow.mmd on every block anyway, in block_reset_session) and exit non-zero with "retry your original command to see the next item" when transitions is empty. That also makes the flow_transitions field in state.json dead, since back-to-back acks were its only purpose. Option 1 is a one-line README edit. The decision on which guarantee steplock wants is still yours, so no PR from this run.

    Everything else on 0.2.2 passed: init scaffolds .steplock/checklists/ with a .gitignore, validate accepts both a linear and a branching checklist, the gate blocks with the current question and writes state.json, one ack per hook invocation advances exactly one item, the action is approved once all items are acked, a branching state lists both options and follows the acknowledged one (a bogus or missing branch name is rejected with exit 1), and audit.log records block/ack/complete.


    Filed automatically by the moadim routine "Nightly lib dogfood → issue + PR (polyhook, block-no-verify, steplock)".

  2. tupe12334 commented on Oct 27, 2026

    @tupe12334
    MemberAuthor

    Correction to my previous comment: the decision is already made. PR #228 ("docs: describe back-to-back ack.sh calls in the README", branch fix/issue-225-readme-ack-order) takes option 1 and is open with all 13 checks green, waiting on a merge. Disregard the option-2 fix sketch above and the "no PR from this run" framing — this run is not opening a competing PR, and #228 closes this issue once merged.

    The 0.2.2 reproduction in my previous comment still stands as the pre-merge baseline: the published binary behaves exactly as #223 intended, and only the README sentence is out of date.


    Filed automatically by the moadim routine "Nightly lib dogfood → issue + PR (polyhook, block-no-verify, steplock)".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions