mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-03 04:07:04 +00:00
Say only what holds on every platform about a background child of run_command
The quick-test leg on Linux failed after #1187: the new test asserted that a '&' child holding stdout SURVIVES the timeout kill. It does on macOS, where the exited shell's process group can no longer be resolved; on Linux the zombie still answers getpgid, the group is killed and the child dies with it. The package had measured one platform only. So the schema sentence no longer says nothing STOPS the child, only that once the call returns nothing tracks it, which is true on both; the TOOL_TIMEOUT text says the host killed what it could still reach and a background child may survive; the test keeps the facts that hold everywhere (the call stalls until timeout_sec, the host holds no handle afterwards) and drops the survival assertion. The two schema byte pins are rolled with the reason.
This commit is contained in:
parent
37d59ab433
commit
8b33558edc
3 changed files with 17 additions and 11 deletions
|
|
@ -532,7 +532,7 @@ def _run_shell(
|
|||
_record_scratch_fingerprints(ctx, scratch_abs)
|
||||
return (
|
||||
f"⚠️ TOOL_TIMEOUT (run_command): command exceeded the per-command timeout of {timeout_sec}s "
|
||||
f"and its process group was killed; a child it detached may survive untracked (root={binding.root}, cwd={work_dir}). NOTE: this is the per-command "
|
||||
f"and the host killed what it could still reach of its process tree; a background child may survive untracked (root={binding.root}, cwd={work_dir}). NOTE: this is the per-command "
|
||||
f"FOREGROUND timeout, NOT the task deadline. For genuinely long-running compute (training, "
|
||||
f"sampling, large builds/downloads), start it with start_service and poll "
|
||||
f"service_status/service_logs while you do other work, or pass an explicit timeout_sec=<seconds> "
|
||||
|
|
@ -681,7 +681,7 @@ def get_tools() -> List[ToolEntry]:
|
|||
"cmd MUST be an array of strings, never one shell string. A builtin as cmd[0] (cd, export, ...) "
|
||||
"is refused: use cwd= or [\"sh\", \"-c\", \"cd x && a | b\"] (also for pipes/chaining). "
|
||||
"A background child (&, nohup) is no service: holding stdout/stderr it stalls the call until "
|
||||
"timeout_sec, and afterwards nothing tracks or stops it (use start_service). "
|
||||
"timeout_sec, and once the call returns nothing tracks it (use start_service). "
|
||||
"Prefer read_file, search_code/query_code, write_file/edit_text to cat/head/sed, grep/find, redirects."
|
||||
),
|
||||
"parameters": {"type": "object", "properties": {
|
||||
|
|
|
|||
|
|
@ -61,9 +61,12 @@ def test_description_states_the_bare_builtin_refusal_and_the_sh_c_escape():
|
|||
|
||||
def test_description_states_what_happens_to_a_background_child():
|
||||
description = _schema()["description"]
|
||||
# What the empirical tests below prove: the call stalls until timeout_sec
|
||||
# while the child holds stdio, and the child survives untracked either way.
|
||||
for word in ("&", "nohup", "stalls the call until timeout_sec", "nothing tracks or stops it", "start_service"):
|
||||
# What the empirical tests below prove on every platform: the call stalls until
|
||||
# timeout_sec while the child holds stdio, and nothing tracks the child afterwards.
|
||||
# Whether the timeout kill still REACHES it is the platform's business (Linux can
|
||||
# resolve the exited shell's process group and kills it, macOS cannot and it
|
||||
# survives), so the description promises neither a cleanup nor a survival.
|
||||
for word in ("&", "nohup", "stalls the call until timeout_sec", "once the call returns nothing tracks it", "start_service"):
|
||||
assert word in description, word
|
||||
assert "killed" not in description and "cleaned up" not in description # no promise the host does not keep
|
||||
|
||||
|
|
@ -92,10 +95,11 @@ def test_background_child_holding_stdout_makes_the_call_wait_until_timeout(tmp_p
|
|||
try:
|
||||
assert result.startswith("⚠️ TOOL_TIMEOUT"), result
|
||||
assert elapsed >= 0.9, elapsed
|
||||
# "nothing tracks or stops it": the shell exited at once, so the timeout's
|
||||
# group kill had no tree left — the backgrounded child is still running.
|
||||
# "once the call returns nothing tracks it": the shell exited at once. On
|
||||
# Linux the timeout kill still resolves the dead shell's process group and
|
||||
# takes the child with it; on macOS it cannot and the child keeps running.
|
||||
# Either way the host holds no handle on it.
|
||||
alive = subprocess.run(["pgrep", "-f", marker], capture_output=True, text=True).stdout.split()
|
||||
assert alive, "the backgrounded child did not outlive the timeout kill"
|
||||
assert not any(str(proc.pid) in alive for proc in list(_active_subprocesses)), "nothing tracks it"
|
||||
finally:
|
||||
subprocess.run(["pkill", "-f", marker], check=False)
|
||||
|
|
|
|||
|
|
@ -112,9 +112,11 @@ def test_shell_catalog_schema_bytes_and_handler_owners_are_stable():
|
|||
# Workflow scope: explicit saved-setting references and lazy-output guidance.
|
||||
# run_command states its real contract: only a bare builtin as cmd[0] is
|
||||
# refused, a background child stalls the call and is never tracked after it
|
||||
# (tests/test_run_command_schema_truth.py).
|
||||
# (tests/test_run_command_schema_truth.py). Rolled once more: the sentence no
|
||||
# longer says the child is never STOPPED, which is false where the timeout kill
|
||||
# still reaches the exited shell's process group (Linux).
|
||||
assert hashlib.sha256(schema_bytes).hexdigest() == (
|
||||
"61a0e6006f609e84e7e9f71b82e376896a0fc9287b295fe75e6fe22d96e870e7"
|
||||
"7d7a07b763cec4e8fc95bdfb733c2b8362bb8c0f0648b33825b56e52dccda4ce"
|
||||
)
|
||||
original = json.loads(schema_bytes)
|
||||
for schema in original:
|
||||
|
|
@ -129,7 +131,7 @@ def test_shell_catalog_schema_bytes_and_handler_owners_are_stable():
|
|||
)
|
||||
assert hashlib.sha256(json.dumps(original, sort_keys=True, ensure_ascii=False,
|
||||
separators=(",", ":")).encode()).hexdigest() == (
|
||||
"d1ee448cd3c283f769dfc2a0de3d2771d7270720931223d64757575ab42a54f5"
|
||||
"c6504272bceed19cc138a9cc8ee98a04db2f6ac3b41d70fdacbc4fa4022542bc"
|
||||
)
|
||||
assert {
|
||||
entry.name: (entry.handler.__module__, entry.handler.__name__)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue