From 700641fa3725c26f5cb01a79e21b222e9066a63a Mon Sep 17 00:00:00 2001 From: host Date: Fri, 30 Oct 2026 02:45:16 +0800 Subject: [PATCH] =?UTF-8?q?fix(worker):=20=E6=97=A0=20result=5Ftext=5Ffull?= =?UTF-8?q?=20=E5=88=97=E7=9A=84=E8=80=81=E5=BA=93/=E6=89=8B=E5=B7=A5?= =?UTF-8?q?=E8=A1=A8=E5=86=99=E5=9B=9E=E5=9B=9E=E8=90=BD=EF=BC=8C=E4=B8=8D?= =?UTF-8?q?=E6=8B=96=E5=9E=AE=E7=BB=88=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI 失败根因:P0 取消守卫测试 mock 掉 _ensure_result_columns 并手工建表(无新列),带新列的 UPDATE 直接 OperationalError。修法:带新列写回失败且错误含 result_text_full 时回滚并用旧 SQL 重写(摘要照写、完整版 丢弃),其它错误原样重抛。 --- cloud/tests/test_queue_sandbox.py | 7 ++-- cloud/worker.py | 55 +++++++++++++++++++++---------- 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/cloud/tests/test_queue_sandbox.py b/cloud/tests/test_queue_sandbox.py index 1fa2e80..c8bb139 100644 --- a/cloud/tests/test_queue_sandbox.py +++ b/cloud/tests/test_queue_sandbox.py @@ -547,8 +547,9 @@ class TestUsageParsing(unittest.TestCase): self.assertEqual(result["status"], "completed") def test_result_from_runner_keeps_stdout_full(self): - # 截断版 stdout 仍是 2000 字,但 stdout_full 必须带完整 - # 文本,否则长答案尾部在库里永远查不到。 + # #165 后 stdout 是解析出的 text 截到 20000 字(不再是 + # 2000 字截 JSON),stdout_full 仍必须带完整文本, + # 否则超 20000 字的长答案尾部在库里永远查不到。 fake = SimpleNamespace( returncode=0, stdout=json.dumps({"status": "completed", @@ -557,7 +558,7 @@ class TestUsageParsing(unittest.TestCase): result = LocalExecutor("/fake/host.py", runner=lambda cmd: fake )._result_from_runner("t1", fake) - self.assertLessEqual(len(result["stdout"]), 2000) + self.assertLessEqual(len(result["stdout"]), 21000) self.assertIn("y" * 5000, result["stdout_full"]) def test_result_from_runner_usage_survives_long_text(self): diff --git a/cloud/worker.py b/cloud/worker.py index db66dd8..0595b6d 100644 --- a/cloud/worker.py +++ b/cloud/worker.py @@ -1467,37 +1467,56 @@ class Worker: status_clause = " AND status = ?" extra = (only_if_status,) full = (result_full[:200000] if result_full else None) + short = ((result_text or "")[:21000] if result_text else None) + err_short = ((error_text or "")[:4000] + if error_text else None) + now = time.time() if child_pid is None: - cur = conn.execute( + sql_full = ( "UPDATE tasks SET result_text = ?," " result_text_full = ?, error_text = ?," " actual_model = ?, started_at = ?, finished_at = ?," " turn_id = COALESCE(?, turn_id)," " root_turn_id = COALESCE(?, root_turn_id)," - " updated_at = ? WHERE id = ?" + status_clause, - ((result_text or "")[:21000] if result_text else None, - full, - (error_text or "")[:4000] if error_text else None, - actual_model, started_at, finished_at, - turn_id, root_turn_id, - time.time(), task_id) + extra, - ) + " updated_at = ? WHERE id = ?" + status_clause) + params_full = (short, full, err_short, actual_model, + started_at, finished_at, turn_id, + root_turn_id, now, task_id) + extra + sql_old = sql_full.replace( + " result_text_full = ?,", "") + params_old = (short, err_short, actual_model, + started_at, finished_at, turn_id, + root_turn_id, now, task_id) + extra else: - cur = conn.execute( + sql_full = ( "UPDATE tasks SET result_text = ?," " result_text_full = ?, error_text = ?," " actual_model = ?, started_at = ?, finished_at = ?," " child_pid = ?," " turn_id = COALESCE(?, turn_id)," " root_turn_id = COALESCE(?, root_turn_id)," - " updated_at = ? WHERE id = ?" + status_clause, - ((result_text or "")[:21000] if result_text else None, - full, - (error_text or "")[:4000] if error_text else None, - actual_model, started_at, finished_at, child_pid, - turn_id, root_turn_id, - time.time(), task_id) + extra, - ) + " updated_at = ? WHERE id = ?" + status_clause) + params_full = (short, full, err_short, actual_model, + started_at, finished_at, child_pid, + turn_id, root_turn_id, now, task_id) + extra + sql_old = sql_full.replace( + " result_text_full = ?,", "") + params_old = (short, err_short, actual_model, + started_at, finished_at, child_pid, + turn_id, root_turn_id, now, task_id) + extra + try: + cur = conn.execute(sql_full, params_full) + except Exception as exc: + # 老库/被 mock 掉迁移的库没有 result_text_full 列: + # 完整版丢弃、摘要照写,绝不让新列拖垮终局写回 + # (P0 取消守卫测试就是 mock 掉迁移的手工表)。 + if "result_text_full" not in str(exc): + raise + try: + conn.rollback() + except Exception: + pass + cur = conn.execute(sql_old, params_old) conn.commit() return cur.rowcount == 1 finally: