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:
1 parent
8b046390be
commit
fb1b93c665
8 files changed
+104
-47
No files matched your search
@@ -43,6 +43,18 @@ INT_KNOBS = {
|
||||
"SKS_DELIVERY_GRACE_S": (0, "15"),
|
||||
# _pane_quiescent's `for ((i = 0; i < tries; i++))` (review R2); 0 keeps "headless" (rc 2)
|
||||
"SKS_QUIESCENT_TRIES": (0, "20"),
|
||||
# Follow-up F4b: `[ -ge ]`/`[ -le ]`/`[ -gt ]` operands and integer sleeps, where
|
||||
# "abc" was a `[` error or a sleep error. lib.sh, reconcile.sh (which sources
|
||||
# it), and loop_lock.sh / diff_collect.sh, which run without lib.sh and
|
||||
# source lib_int.sh themselves.
|
||||
"MAM_READY_EMPTY_GIVEUP": (1, "5"), # consecutive empty captures; 0 would act as 1
|
||||
"SKS_EMPTY_GIVEUP": (1, "3"), # the same, in _pane_quiescent
|
||||
"SKS_IDLE_RETRIES": (0, "2"), # 0: one wait, no retry
|
||||
"SKS_IDLE_RETRY_BACKOFF_S": (0, "10"), # sleep between retries; test_issue6 uses 0
|
||||
"MAM_LOCK_STEAL_RETRIES": (0, "3"), # 0: one attempt, no steal retry
|
||||
"MAM_DIFF_MAX_BYTES": (1, "200000"), # 0 would truncate every diff
|
||||
"MAM_DIFF_MAX_LINES": (1, "4000"),
|
||||
"RECONCILE_POLL_INTERVAL": (1, "15"), # sleep; 0 would poll in a busy loop
|
||||
# delegate_wait_started's ack_s (MAM_ONBOARD_ACK_S / DELEGATE_JOB_ACK_S via create_session.sh)
|
||||
"delegate_wait_started:ack_s": (0, "180"),
|
||||
# wrapper flags
|
||||
@@ -85,11 +97,8 @@ NOT_ARITHMETIC = {
|
||||
"MAM_MAX_PANE_COLS": "argv of python -m lib_py.layout", "MAM_MAX_PANE_ROWS": "argv of python -m lib_py.layout",
|
||||
"MAM_BUFFER_GC_MINUTES": "find -mmin", "MAM_DIALOG_TAIL_LINES": "tail -n",
|
||||
"SKS_IDLE_TIMEOUT_MS": "argv of the python idle watcher", "MAM_DIALOG_RESEND_S": "python int()",
|
||||
"MAM_READY_EMPTY_GIVEUP": "[ -ge ] (decimal)", "SKS_EMPTY_GIVEUP": "[ -ge ] (decimal)",
|
||||
"SKS_IDLE_RETRIES": "[ -le ] (decimal)", "SKS_IDLE_RETRY_BACKOFF_S": "sleep",
|
||||
"SKS_QUIESCENT_INTERVAL": "sleep", "MAM_LOCK_STEAL_RETRIES": "[ -le ] (decimal)",
|
||||
"MAM_DIFF_MAX_BYTES": "[ -gt ] (decimal)", "MAM_DIFF_MAX_LINES": "[ -gt ] (decimal)",
|
||||
"RECONCILE_POLL_INTERVAL": "sleep",
|
||||
# sleep's fractional seconds are its meaning here (default 0.5): mam_int would refuse them
|
||||
"SKS_QUIESCENT_INTERVAL": "sleep, fractional by default (0.5) - left raw",
|
||||
"DELEGATE_JOB_ACK_S": "create_session.sh -> delegate_wait_started, which runs mam_int on it",
|
||||
}
|
||||
|
||||
@@ -407,7 +416,7 @@ def test_b5_no_env_default_inside_arithmetic(path):
|
||||
|
||||
|
||||
def test_b5_every_normalised_knob_uses_the_table_minimum():
|
||||
src = WRAPPER.read_text() + "\n" + LIB_SH.read_text()
|
||||
src = "\n".join(f.read_text() for f in [WRAPPER, *sorted(SKILLS.glob("**/*.sh"))])
|
||||
calls = re.findall(r"mam_int_knob ([A-Z_]+) (\S+) (\d+)\)", src)
|
||||
seen = {}
|
||||
for name, default, minimum in calls:
|
||||
@@ -445,6 +454,36 @@ def test_b5_every_integer_env_default_is_classified():
|
||||
assert not stale, f"no longer in the code: {stale}"
|
||||
|
||||
|
||||
LOOP_LOCK = SKILLS / "multi-agent-mux-loop" / "scripts" / "loop_lock.sh"
|
||||
DIFF_COLLECT = SKILLS / "multi-agent-mux-loop" / "scripts" / "diff_collect.sh"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", ["abc", "0900", " 7 ", "-1", "1.5", "0", ""], ids=_id)
|
||||
@pytest.mark.parametrize("script,knobs", [(LOOP_LOCK, ["MAM_LOCK_STEAL_RETRIES"]),
|
||||
(DIFF_COLLECT, ["MAM_DIFF_MAX_BYTES", "MAM_DIFF_MAX_LINES"])],
|
||||
ids=lambda v: v.name if isinstance(v, Path) else "")
|
||||
def test_b5_scripts_sourced_without_lib_sh_read_their_knobs_as_decimal(tmp_path, script, knobs, value):
|
||||
"""F4b: these files are sourced alone (the runner, the tests): they bring
|
||||
the integer rule with them, and "abc" no longer reaches `[ -le ]`/`[ -gt ]`."""
|
||||
probe = "; ".join(f'printf "%s=%s\\n" {k} "${k}"' for k in knobs)
|
||||
res = subprocess.run(["bash", "-c", f'source "{script}"; {probe}'], capture_output=True, text=True,
|
||||
env=_base_env(tmp_path, {k: value for k in knobs}), cwd=str(tmp_path), timeout=60)
|
||||
assert res.returncode == 0, res.stderr
|
||||
for k in knobs:
|
||||
minimum, default = INT_KNOBS[k]
|
||||
want = _expected(value, minimum)
|
||||
assert f"{k}={default if want is None else want}" in res.stdout.splitlines(), (res.stdout, res.stderr)
|
||||
assert (f"WARNING: {k}=" in res.stderr) is (want is None and value != ""), res.stderr
|
||||
|
||||
|
||||
def test_b5_pane_quiescent_gives_up_on_empty_captures_with_a_bad_knob(tmp_path):
|
||||
"""F4b: SKS_EMPTY_GIVEUP=abc was a `[` error on every empty capture."""
|
||||
res = _bash('_pane_capture() { :; }; _pane_quiescent s 20 0; echo "rc=$?"', tmp_path, {"SKS_EMPTY_GIVEUP": "abc"})
|
||||
assert "rc=2" in res.stdout, (res.stdout, res.stderr)
|
||||
assert "integer expression expected" not in res.stderr, res.stderr
|
||||
assert "WARNING: SKS_EMPTY_GIVEUP=abc ignored" in res.stderr, res.stderr
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value,rc", [("900", 0), ("0900", 0), ("0", 2)])
|
||||
def test_b5_pane_quiescent_reads_the_tries_knob_as_decimal(tmp_path, value, rc):
|
||||
"""The consuming function with the call site's normalisation (review R2):
|
||||
|
||||
Reference in new issue
Block a user