fix(followups): F4b - bash integer knobs that reach [ -ge/-le/-gt ] or sleep go through mam_int_knob

A non-numeric MAM_READY_EMPTY_GIVEUP, SKS_EMPTY_GIVEUP, SKS_IDLE_RETRIES,
SKS_IDLE_RETRY_BACKOFF_S, MAM_LOCK_STEAL_RETRIES, MAM_DIFF_MAX_BYTES,
MAM_DIFF_MAX_LINES or RECONCILE_POLL_INTERVAL was a `[` error or a sleep
error at the point of use (PR #43 review claude R3). They now go through
mam_int_knob: decimal, a warning and the default for a bad value. Minimums
are in test_issue26_ints.py INT_KNOBS: 1 for the give-up counts (0 acts as
1), the diff caps (0 truncates every diff) and the poll interval (0 is a
busy loop); 0 for the retry counts (one attempt) and the retry backoff.

SKS_QUIESCENT_INTERVAL stays raw: its default is 0.5 s and sleep's
fraction is its meaning; NOT_ARITHMETIC says so.

loop_lock.sh and diff_collect.sh also run without lib.sh (the Python runner
and the tests source them alone), so mam_int / mam_int_knob move verbatim
to lib_int.sh, which lib.sh sources and those two source when the function
is missing. remove.sh's no-manifest fallback list names the new file
(install copies every file under .agents/skills). test_issue21_loop_b l15,
which builds a control root holding only diff_collect.sh, copies it too.

Tests: the table guard now reads every shell file; scripts sourced alone
read their knobs as decimal; _pane_quiescent with SKS_EMPTY_GIVEUP=abc
gives up with a warning and no `[` error (14 of the new cases fail at
0051fd6).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
This commit is contained in:
GodopuandClaude Opus 5.5 committed 2026-10-02 08:23:31 +09:00
1 parent 8b046390be
commit fb1b93c665
8 files changed
+104 -47

No files matched your search

+8 -37
View File
@@ -2335,39 +2335,10 @@ sys.exit(1)
' 2>/dev/null
}
# mam_int <value> <min>
#
# Issue #26: the one integer rule for knobs that reach bash arithmetic (the
# Python twin is lib_py/loop/runner.py::_positive_int). Surrounding blanks are
# allowed, then digits only, read as DECIMAL (bash would read 0700 as octal
# 448 and fail on 0900), and min <= n <= 999999999. Prints n and returns 0, or
# returns 1. The per-knob minimum lives in tests/test_issue26_ints.py.
mam_int() {
# Spelled-out ASCII sets, not [[:space:]]/[0-9]: those follow the locale, and
# the runner's _positive_int takes ASCII only (review R2: U+2003 30 U+2003
# was 30 here under en_US.UTF-8 and invalid there).
local _ws=$' \t\n\r\v\f' _re
_re="^[${_ws}]*([0123456789]+)[${_ws}]*\$"
[[ "${1-}" =~ $_re ]] || return 1
local d="${BASH_REMATCH[1]}"
d="${d#"${d%%[!0]*}"}"
[ ${#d} -le 9 ] || return 1
local n=$(( 10#${d:-0} ))
[ "$n" -ge "$2" ] || return 1
printf '%s\n' "$n"
}
# mam_int_knob <ENV_NAME> <default> <min>
#
# The value of $ENV_NAME through mam_int; unset or empty gives <default>, an
# invalid value gives <default> with a warning on stderr.
mam_int_knob() {
local raw="${!1-}"
[ -n "$raw" ] || { printf '%s\n' "$2"; return 0; }
mam_int "$raw" "$3" && return 0
echo "WARNING: $1=$raw ignored (not a decimal integer >= $3); using $2" >&2
printf '%s\n' "$2"
}
# mam_int / mam_int_knob (Issue #26) live in lib_int.sh, shared with the
# scripts that run without lib.sh.
# shellcheck source=lib_int.sh
source "$(dirname "${BASH_SOURCE[0]}")/lib_int.sh"
# delegate_wait_started <job_id> [ack_timeout_s] [dispatch_token]
#
@@ -2443,7 +2414,7 @@ wait_for_tui_ready() {
fi
local empty_streak=0
local empty_giveup="${MAM_READY_EMPTY_GIVEUP:-5}"
local empty_giveup="$(mam_int_knob MAM_READY_EMPTY_GIVEUP 5 1)"
local modal_pat="$_MAM_MODAL_TOKENS"
[ -n "$modal_tokens" ] && modal_pat="${modal_pat}|${modal_tokens}"
@@ -2639,7 +2610,7 @@ _wait_session_gone() {
_pane_quiescent() {
local sess="$1" tries="${2:-20}" interval="${3:-0.5}" prev="__none__" cur i
local saw_output=0 empty_streak=0
local empty_giveup="${SKS_EMPTY_GIVEUP:-3}"
local empty_giveup="$(mam_int_knob SKS_EMPTY_GIVEUP 3 1)"
for ((i = 0; i < tries; i++)); do
cur=$(_pane_capture "$sess")
if [ -z "$cur" ]; then
@@ -2709,8 +2680,8 @@ except Exception:
return 0
fi
local max_retries="${SKS_IDLE_RETRIES:-2}"
local backoff_s="${SKS_IDLE_RETRY_BACKOFF_S:-10}"
local max_retries="$(mam_int_knob SKS_IDLE_RETRIES 2 0)"
local backoff_s="$(mam_int_knob SKS_IDLE_RETRY_BACKOFF_S 10 0)"
local try=0
while [ "$try" -le "$max_retries" ]; do
local watcher_out="" watcher_rc=0