fix(issue-26): integer knobs that reach bash arithmetic are read as decimal
bash read MAM_JOB_TIMEOUT_S=0700 as octal 448 (and failed on 0900) while the loop runner read 700. One rule now, with a per-knob minimum: lib.sh mam_int / mam_int_knob and runner._positive_int (blanks allowed, digits only, decimal, min <= n <= 999999999). - minimum 1: MAM_JOB_TIMEOUT_S, MAM_HEARTBEAT_SEC, MAM_HEARTBEAT_MISSES, MAM_ACK_TIMEOUT_S (invalid env: warning + default) and the wrapper's --timeout/--idle-timeout/--heartbeat/--heartbeat-misses/--ack-timeout (invalid: rc 1); the runner's job_budget, --job-timeout, contract_args. - minimum 0 (0 = check once): MAM_INJECT_ACK_GRACE_S, delegate_wait_started's ack_s, SKS_DIALOG_TIMEOUT, SKS_BUSY_TIMEOUT, SKS_DELIVERY_GRACE_S. - tests/test_issue26_ints.py: INT_KNOBS is the one table of minimums; a static guard keeps env defaults out of arithmetic and checks each normalised knob against it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f1331d36ce
commit
80dc2eb45e
4 files changed
+367
-24
No files matched your search
@@ -67,6 +67,16 @@ def log_error(msg: str) -> None:
|
||||
print(f"{_COLOR_ERROR} {msg}", file=sys.stderr)
|
||||
|
||||
|
||||
|
||||
def _positive_int(text: Optional[str], minimum: int = 1) -> Optional[int]:
|
||||
"""Issue #26: lib.sh mam_int's rule - blanks around, digits only, decimal
|
||||
(0700 is 700), minimum <= n <= 999999999; else None."""
|
||||
s = (text or "").strip(" \t\n\r\v\f")
|
||||
if not s or not s.isascii() or not s.isdigit():
|
||||
return None
|
||||
n = int(s)
|
||||
return n if minimum <= n <= 999_999_999 else None
|
||||
|
||||
class LoopRunner:
|
||||
"""Orchestrates planning, code implementation, and multi-agent peer reviews."""
|
||||
|
||||
@@ -142,7 +152,7 @@ class LoopRunner:
|
||||
" --all-reviewer Enforce PASS verdict from all active reviewer sessions",
|
||||
" --max-loop N Max execution-review corrective loop runs (default: 3)",
|
||||
" --max-rebut N Max rebuttal attempts per review iteration (default: 1, 0: disabled)",
|
||||
" --job-timeout <sec> Budget of each delegated job (default: $MAM_JOB_TIMEOUT_S or 3600)",
|
||||
" --job-timeout <sec> Budget of each delegated job (positive decimal integer; default: $MAM_JOB_TIMEOUT_S or 3600)",
|
||||
" --verbose Print detailed execution timeline traces",
|
||||
" --cleanup Purge temporary job directories upon success",
|
||||
" --worktree <path> Source worktree for diff/base/head (control plane stays here)",
|
||||
@@ -214,10 +224,10 @@ class LoopRunner:
|
||||
self.max_loop = int(args[i + 1])
|
||||
i += 2
|
||||
elif arg == "--job-timeout":
|
||||
if i + 1 >= len(args) or not re.match(r"^[0-9]+$", args[i + 1]) or int(args[i + 1]) <= 0:
|
||||
print("ERROR: --job-timeout requires a positive non-zero integer.", file=sys.stderr)
|
||||
self.job_timeout = _positive_int(args[i + 1]) if i + 1 < len(args) else None
|
||||
if self.job_timeout is None:
|
||||
print("ERROR: --job-timeout requires a positive decimal integer.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
self.job_timeout = int(args[i + 1])
|
||||
i += 2
|
||||
elif arg == "--max-rebut":
|
||||
if i + 1 >= len(args) or not re.match(r"^[0-9]+$", args[i + 1]):
|
||||
@@ -471,9 +481,9 @@ class LoopRunner:
|
||||
for env_name, flag in (("MAM_HEARTBEAT_SEC", "--heartbeat"),
|
||||
("MAM_HEARTBEAT_MISSES", "--heartbeat-misses"),
|
||||
("MAM_ACK_TIMEOUT_S", "--ack-timeout")):
|
||||
val = os.environ.get(env_name)
|
||||
if val and val.isdigit() and int(val) > 0:
|
||||
args += [flag, val]
|
||||
val = _positive_int(os.environ.get(env_name))
|
||||
if val is not None:
|
||||
args += [flag, str(val)]
|
||||
return args
|
||||
|
||||
def resolve_all_reviewers(self, state: Dict[str, Any]) -> List[str]:
|
||||
@@ -593,11 +603,17 @@ class LoopRunner:
|
||||
|
||||
@property
|
||||
def job_budget(self) -> int:
|
||||
"""Issue #33: --job-timeout > $MAM_JOB_TIMEOUT_S (positive integer) > 3600."""
|
||||
"""Issue #33: --job-timeout > $MAM_JOB_TIMEOUT_S (positive decimal integer) > 3600."""
|
||||
if self.job_timeout:
|
||||
return self.job_timeout
|
||||
env = os.environ.get("MAM_JOB_TIMEOUT_S", "")
|
||||
return int(env) if env.isdigit() and int(env) > 0 else 3600
|
||||
n = _positive_int(env)
|
||||
if n is None and env:
|
||||
if not getattr(self, "_warned_job_timeout", False):
|
||||
self._warned_job_timeout = True
|
||||
print(f"WARNING: MAM_JOB_TIMEOUT_S={env} ignored (not a decimal integer >= 1); using 3600", file=sys.stderr)
|
||||
return 3600
|
||||
return n or 3600
|
||||
|
||||
@property
|
||||
def job_wait_s(self) -> int:
|
||||
|
||||
Reference in new issue
Block a user