shareAI-lab/learn-claude-code · error · GoalError
block_cap must be at least 1
Error message
block_cap must be at least 1
What it means
GoalController was constructed with block_cap < 1. block_cap limits how many times the Stop hook may consecutively block the agent from stopping (DEFAULT_STOP_HOOK_BLOCK_CAP is 8); a cap of 0 or negative would make the loop logic meaningless, so the constructor fails fast (s17_goal_loop/code.py:271).
Source
Thrown at s17_goal_loop/code.py:271
),
messages=[{"role": "user", "content": prompt}],
max_tokens=self.max_tokens,
)
value = _parse_json_object(_extract_text(response.content))
return GoalEvaluation(**value)
class GoalController:
"""Session-scoped goal state plus the Stop hook decision."""
def __init__(
self,
evaluator: Any,
block_cap: int = DEFAULT_STOP_HOOK_BLOCK_CAP,
events: list[dict[str, Any]] | None = None,
):
if block_cap < 1:
raise GoalError("block_cap must be at least 1")
self.evaluator = evaluator
self.block_cap = block_cap
self.events = events if events is not None else []
self.active: GoalState | None = None
self.last_status: dict[str, Any] | None = None
self.consecutive_blocks = 0
def begin_query(self) -> None:
self.consecutive_blocks = 0
def set_goal(self, condition: str, tokens_at_start: int = 0) -> GoalState:
condition = condition.strip()
if not condition:
raise GoalError("goal condition cannot be empty")
if len(condition) > MAX_GOAL_LENGTH:
raise GoalError(
f"goal condition cannot exceed {MAX_GOAL_LENGTH} characters"
)View on GitHub (pinned to 985456f4ad)
Solutions
- Pass block_cap >= 1 (1 means the hook blocks at most once, then lets the agent stop)
- If disabling blocking was the intent, do not construct the controller with a hook, or set the cap to 1
- Set CLAUDE_CODE_STOP_HOOK_BLOCK_CAP to a positive integer such as 8, or unset it to accept the default
Example fix
# before controller = GoalController(evaluator, block_cap=0) # after controller = GoalController(evaluator, block_cap=1)
Defensive patterns
Strategy: validation
Validate before calling
raw_cap = int(os.getenv("CLAUDE_CODE_STOP_HOOK_BLOCK_CAP", 8))
if raw_cap < 1:
raise SystemExit("CLAUDE_CODE_STOP_HOOK_BLOCK_CAP must be >= 1")
controller = GoalController(evaluator, block_cap=raw_cap) Type guard
def is_valid_block_cap(value: object) -> bool:
return isinstance(value, int) and not isinstance(value, bool) and value >= 1 Try / catch
try:
GoalController(evaluator, block_cap=cap)
except GoalError:
cap = DEFAULT_STOP_HOOK_BLOCK_CAP
controller = GoalController(evaluator, block_cap=cap) Prevention
- Never use 0 to mean 'disabled' — use 1 for minimal blocking or omit the hook
- Validate user-supplied numeric config before constructing the controller
- Remember the env var flows through int(); reject negatives at the config boundary
When it happens
Trigger: Directly instantiating GoalController(evaluator, block_cap=0) or block_cap=-3. Indirectly: setting the environment variable CLAUDE_CODE_STOP_HOOK_BLOCK_CAP to 0 or a negative number, which make_live_session converts with int() and passes in.
Common situations: Trying to 'disable' the stop-hook blocking by setting the cap to 0 (use a cap of 1 for single-block behavior instead); misreading the env var as a boolean flag; passing a value parsed from user config without validation.
Related errors
- max_turns must be at least 1
- goal evaluator must return a JSON object
- goal evaluator response requires non-empty 'reason'
- goal condition cannot be empty
- goal condition cannot exceed {MAX_GOAL_LENGTH} characters
AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14).
Data as JSON: /api/errors/4bfcacbc50f28c06.
Report an issue: GitHub.