Merge pull request #16 from openqj/docker-config-repo-and-stderr
Docker default repo + keep stderr in worker error_text
This commit is contained in:
3 files changed
+34
-2
No files matched your search
@@ -324,6 +324,28 @@ class FallbackRetryTestCase(unittest.TestCase):
|
||||
self.assertEqual(len(self.calls), 1)
|
||||
self.assertEqual(result["events"], [])
|
||||
|
||||
def test_crash_without_code_error_text_keeps_stderr(self):
|
||||
# stdout 为空、stderr 有真实报错时,最终 error_text 必须
|
||||
# 留住 stderr,不能被通用的 host execution failed 盖掉
|
||||
# (主机实测:缺 repo 的 1 秒失败曾因此完全无法排障)。
|
||||
def fake(cmd):
|
||||
return SimpleNamespace(
|
||||
returncode=1, stdout="",
|
||||
stderr="Traceback (most recent call last):\n"
|
||||
"RuntimeError: 缺少 --repo,也没有在 host-config.json 里配 repo")
|
||||
|
||||
_insert_task(self.db_path, "A")
|
||||
worker = self._worker(
|
||||
runner=self._recording(fake),
|
||||
model="gpt-6.1-sol",
|
||||
fallback_models=["gpt-6-sol"],
|
||||
max_retries=0)
|
||||
result = worker.run_one()
|
||||
self.assertEqual(result["status"], "failed")
|
||||
row, cost, audits = _execution(self.db_path, "A")
|
||||
self.assertIn("缺少 --repo", row[0])
|
||||
self.assertEqual(result["events"], [])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+11
-1
@@ -905,6 +905,13 @@ class Worker:
|
||||
parsed = None
|
||||
if isinstance(parsed, dict) and parsed.get("text"):
|
||||
return str(parsed["text"])
|
||||
# host.py 崩溃时 stdout 常为空、真正的报错在 stderr(例如
|
||||
# “缺少 --repo”、Read-only file system);只用 stdout 时
|
||||
# 最终错误会被写成通用的 host execution failed,排障无从
|
||||
# 下手,所以 stdout 为空时取 stderr 尾部(实际报错行在尾)。
|
||||
stderr = (result or {}).get("stderr", "") or ""
|
||||
if not stdout.strip() and stderr.strip():
|
||||
return stderr.strip()[-1000:]
|
||||
if token is not None:
|
||||
return "host_status=%s" % token
|
||||
return stdout.strip() or "host execution failed"
|
||||
@@ -1124,7 +1131,10 @@ class Worker:
|
||||
fallback_model, second_error))
|
||||
else:
|
||||
if error is None:
|
||||
error = stdout.strip() or "host execution failed"
|
||||
stderr = (result or {}).get("stderr", "") or ""
|
||||
error = stdout.strip() or \
|
||||
(stderr.strip()[-1000:] if stderr.strip() else "") \
|
||||
or "host execution failed"
|
||||
if retry_count:
|
||||
error = "[retries=%d] %s" % (retry_count, error)
|
||||
# 未配 fallback_models 时的老行为:只记事件、不重试。
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"_note":"Docker 执行器专用配置(容器内路径,绝不是宿主路径):state 必须用相对路径 work/host-state.json——相对配置文件所在目录 /app 解析,得到 /app/work/host-state.json,经额外挂载落到宿主 /opt/codex-app-server/work/host-state.json,属主是 codexsvc(uid 1003,与容器 --user 1003:1004 一致),容器可写。绝不能用默认的 /app/host-state.json(/app 是只读挂载,锁一开就报 Read-only file system),也绝不能用 /work/host-state.json(/work 挂载的宿主目录由 docker 自动创建、属主 root,容器用户写不进去,同样 1 秒内失败)。此文件曾因 deploy 清理被删过(不在仓库里),导致 Docker 派发静默全灭,所以它必须进仓库、随部署一起发。sandbox 用 danger-full-access:主机内核禁止不可信 user namespace,Docker + 内层 bwrap 双重沙箱不可行,生产隔离层就是 Docker 本身。API key 绝不放这里,只走环境变量。","approval":"deny","approval_policy":"never","effort":"low","model":"gpt-6.1-sol","sandbox":"danger-full-access","state":"work/host-state.json"}
|
||||
{"_note": "Docker 执行器专用配置(容器内路径,绝不是宿主路径):state 必须用相对路径 work/host-state.json——相对配置文件所在目录 /app 解析,得到 /app/work/host-state.json,经额外挂载落到宿主 /opt/codex-app-server/work/host-state.json,属主是 codexsvc(uid 1003,与容器 --user 1003:1004 一致),容器可写。绝不能用默认的 /app/host-state.json(/app 是只读挂载,锁一开就报 Read-only file system),也绝不能用 /work/host-state.json(/work 挂载的宿主目录由 docker 自动创建、属主 root,容器用户写不进去,同样 1 秒内失败)。此文件曾因 deploy 清理被删过(不在仓库里),导致 Docker 派发静默全灭,所以它必须进仓库、随部署一起发。sandbox 用 danger-full-access:主机内核禁止不可信 user namespace,Docker + 内层 bwrap 双重沙箱不可行,生产隔离层就是 Docker 本身。API key 绝不放这里,只走环境变量。 默认仓库是宿主测试仓库:没有 repo_url 的项目走它;有 repo_url 的项目 worker 会把解析出的检出经 --repo 传进去,覆盖它。", "approval": "deny", "approval_policy": "never", "effort": "low", "model": "gpt-6.1-sol", "sandbox": "danger-full-access", "state": "work/host-state.json", "repo": "/var/lib/codex-app-server/test-repo"}
|
||||
Reference in new issue
Block a user