Skip to content

feat(cli): clean up OpenAI logs in non-interactive sessions - #8893

Merged
doudouOUC merged 3 commits into
QwenLM:mainfrom
doudouOUC:feat/noninteractive-openai-log-housekeeping
Aug 11, 2026
Merged

feat(cli): clean up OpenAI logs in non-interactive sessions#8893
doudouOUC merged 3 commits into
QwenLM:mainfrom
doudouOUC:feat/noninteractive-openai-log-housekeeping

Conversation

@doudouOUC

Copy link
Copy Markdown
Collaborator

What this PR does

This PR extends OpenAI API log retention from interactive sessions to one-shot headless invocations, stream-json SDK transports, and ACP-based daemon sessions. Non-interactive targets are resolved from the initialized model configuration with settings fallbacks, deduplicated by absolute log directory, and processed through one process-local FIFO worker so multiple ACP workspaces never multiply disk concurrency. Interactive and non-interactive processes continue to share the existing per-directory marker and exclusive lock.

The cleaner now supports cooperative cancellation and reports whether it reached the end of the directory. An interrupted scan releases its lock without writing a completion marker, allowing a later long-lived process to resume cleanup. Persistent processes retry completed work daily, fresh markers when their remaining interval expires, lock contention after one minute, and filesystem failures after ten minutes. Exit cleanup cancels active work, drops queued targets, and waits for at most 250 milliseconds without blocking model requests or ACP session creation.

The CLI lifecycle starts cleanup only after authentication succeeds and, for one-shot modes, after non-empty input is confirmed. Stream-json starts immediately before its protocol loop, while ACP registers each successfully initialized workspace session and intentionally ignores the bootstrap working directory. Successful and exceptional exits now both run the existing cleanup chain. No SDK transport, core writer, public API, log format, directory layout, or user-facing option is changed.

Why it's needed

The initial retention implementation in #8862 runs only from interactive background housekeeping. TypeScript, Python, and Java process-based SDKs use stream-json, while daemon SDKs use ACP, so workloads that primarily use headless or SDK entry points can continue accumulating full OpenAI request and response logs indefinitely. Short one-shot processes also need cancellation-aware best-effort cleanup so they do not leave a false success marker that suppresses a later complete scan.

Reviewer Test Plan

How to verify

  • Configure a temporary custom OpenAI log directory with user- or system-owned seven-day retention, seed an expired writer-format file, a recent writer-format file, and an expired lookalike such as openai-my-export.json, then run one-shot headless, stream-json, and ACP flows. Confirm only the expired writer-owned file is removed and the directory receives one completion marker.
  • Start ACP from workspace A and create a session for workspace B. Confirm only B's resolved log directory is registered and cleaned. Create sessions for multiple workspaces and confirm directory scans run serially; repeated sessions for the same directory must not enqueue duplicate work.
  • Point a custom directory at trusted workspace-only retention and confirm cleanup is skipped without a marker. Add system-owned retention and confirm the same directory is cleaned and marked.
  • Start two processes against one directory and confirm one holds the lock while the other reports lock contention, with only one completed scan and marker. Interrupt a short process during a large scan and confirm it writes no marker; a subsequent long-lived stream-json process must remove the remainder and write the marker.
  • Run the focused housekeeping, settings, CLI lifecycle, and ACP tests; all 582 assertions should pass. Regenerate the settings schema, then run type checking and the full build.

Evidence (Before & After)

N/A — this changes background housekeeping behavior and has no TUI or visual output.

Tested on

OS Status
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

Environment (optional)

macOS 26.4.1 arm64; Node.js 22.22.3; npm 10.9.8; repository version 0.21.9; built CLI with a local fake OpenAI server for black-box headless, stream-json, ACP, ownership, lock-contention, and interrupted-scan verification.

Risk & Scope

  • Main risk or tradeoff: Short-lived processes provide best-effort progress because the flat directory has no persistent portable traversal cursor. Work is bounded to 20 file operations per batch, one directory per process, and one cross-process lock per directory; an interrupted process may revisit an earlier directory prefix on its next run.
  • Not validated / out of scope: Windows and Linux were not tested locally. SIGKILL, crashes, and external termination that bypasses exit cleanup rely on the existing one-hour stale-lock recovery. Direct @qwen-code/qwen-code-core embeddings and in-memory channels that bypass the CLI lifecycle remain outside this change.
  • Breaking changes / migration notes: No breaking API, configuration, transport, log-format, or directory-layout changes. Existing custom-directory retention ownership rules remain unchanged, and settings changes still require a restart.

Linked Issues

Closes #8860

Follow-up to #8862

中文说明

本 PR 的改动

本 PR 将 OpenAI API 日志保留能力从交互式会话扩展到一次性 headless 调用、stream-json SDK transport 和基于 ACP 的 daemon 会话。非交互目标从已初始化的模型配置及设置回退中解析,按绝对日志目录去重,并通过单个进程内 FIFO worker 处理,因此多个 ACP 工作区不会放大磁盘并发。交互式与非交互式进程继续共享现有的目录级 marker 和排他锁。

清理器现在支持协作式取消,并报告是否扫描到了目录末尾。被中断的扫描会释放锁但不写完成 marker,使后续长驻进程能够继续完成清理。长驻进程会在完成后每日重试、在 fresh marker 的剩余有效期结束后重试、锁竞争后一分钟重试,并在文件系统失败后十分钟重试。退出清理会取消当前任务、丢弃排队目标,并最多等待 250 毫秒,同时不会阻塞模型请求或 ACP session 创建。

CLI 生命周期只会在认证成功后启动清理;对于一次性模式,还要求已经确认输入非空。stream-json 在进入协议循环前启动,而 ACP 会为每个成功初始化的工作区 session 登记目标,并有意忽略 bootstrap 工作目录。成功返回和异常退出现在都会运行现有退出清理链。本 PR 不修改 SDK transport、core writer、公共 API、日志格式、目录结构或用户可见参数。

为什么需要

#8862 中的初始日志保留实现只从交互式后台 housekeeping 启动。TypeScript、Python 和 Java 进程型 SDK 使用 stream-json,而 daemon SDK 使用 ACP,因此主要采用 headless 或 SDK 入口的负载仍可能无限累积完整的 OpenAI 请求与响应日志。短生命周期的一次性进程也需要支持取消的尽力清理,避免留下错误的成功 marker,进而阻止后续进程执行完整扫描。

评审者测试计划

如何验证

  • 配置一个临时自定义 OpenAI 日志目录,并在用户级或系统级设置 7 天保留期;预置一个过期的 writer 格式文件、一个近期 writer 格式文件,以及一个过期同名前缀文件(例如 openai-my-export.json),然后分别运行一次性 headless、stream-json 和 ACP。确认只删除过期的 writer 自有文件,并且目录只产生一个完成 marker。
  • 从工作区 A 启动 ACP,并为工作区 B 创建 session。确认只登记和清理 B 解析出的日志目录。为多个工作区创建 session,确认目录扫描串行执行;同一目录的重复 session 不得重复入队。
  • 让自定义目录仅使用可信工作区级 retention,确认跳过清理且不写 marker。再增加系统级 retention,确认同一目录被清理并写入 marker。
  • 让两个进程同时处理同一目录,确认只有一个持锁,另一个报告锁竞争,最终只有一次完整扫描和一个 marker。让短进程在大目录扫描中退出,确认它不写 marker;随后启动长驻 stream-json 进程,确认它删除剩余文件并写入 marker。
  • 运行 housekeeping、设置、CLI 生命周期和 ACP 的聚焦测试,582 项断言应全部通过。重新生成 settings schema,然后运行类型检查和全仓构建。

证据(改动前后)

N/A——本 PR 修改后台 housekeeping 行为,不包含 TUI 或视觉输出。

测试平台

操作系统 状态
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

环境(可选)

macOS 26.4.1 arm64;Node.js 22.22.3;npm 10.9.8;仓库版本 0.21.9;使用构建后的 CLI 和本地 fake OpenAI server 完成 headless、stream-json、ACP、配置归属、锁竞争及中断扫描的黑盒验证。

风险与范围

  • 主要风险或权衡:由于平铺目录不存在可移植的持久遍历游标,短生命周期进程只提供尽力清理。每批最多执行 20 个文件操作,每个进程同一时间只处理一个目录,每个目录使用一个跨进程锁;中断后的进程下次运行时可能重新访问目录前段。
  • 未验证 / 范围外:Windows 和 Linux 未在本地测试。SIGKILL、崩溃及绕过退出清理的外部终止依赖现有的一小时陈旧锁恢复。直接嵌入 @qwen-code/qwen-code-core 以及绕过 CLI 生命周期的内存 channel 不在本次范围内。
  • 破坏性变更 / 迁移说明:没有 API、配置、transport、日志格式或目录结构的破坏性变更。现有自定义目录 retention 归属规则保持不变,设置变更仍然需要重启。

关联问题

Closes #8860

#8862 的后续 PR

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@github-actions github-actions Bot added the review/self-reported The linked issue was opened by the PR author (self-reported) label Aug 10, 2026

Copy link
Copy Markdown
Collaborator Author

Non-interactive OpenAI log retention E2E

Baseline

Global qwen 0.21.8 was exercised with a temporary QWEN_HOME and custom OpenAI log directory. One-shot JSON, stream-json, and ACP calls all completed real model requests and wrote new logs, but preserved a seeded expired writer-format log and created no cleanup marker.

Verification

Use a temporary QWEN_HOME, a temporary custom log directory, and the existing fake OpenAI server harness. Configure retention at user scope, then seed:

  • an expired writer-format JSON log, which must be removed;
  • a recent writer-format JSON log, which must remain;
  • openai-my-export.json, which must remain.

Run and verify these paths against the built CLI:

  1. One-shot headless JSON: qwen --safe-mode --openai-logging --openai-logging-dir <dir> -p "Reply exactly OK" -o json.
  2. Stream-json: start with matching stream-json input/output flags, send an initialize control request followed by one user message, and wait for the final result.
  3. ACP: send initialize, authenticate, session/new, and session/prompt; wait for end_turn, then close the process gracefully.

For each path, assert the expired log is deleted, the recent and lookalike files remain, and the per-directory cleanup marker exists. For two concurrent processes sharing the directory, assert only one obtains the lock and both exit without errors. For cancellation, use the focused scheduler tests rather than fixed sleeps: an interrupted scan must release its lock when possible and must not write a completion marker.

Result

All cases passed against the built packages/cli/dist with a local fake OpenAI server:

  • Headless -p removed the expired writer-format file, preserved the recent and lookalike files, wrote one new request log, and wrote the per-directory marker.
  • Stream-json removed 2,000 expired files and wrote the marker while stdin remained open, without making a model request.
  • ACP started from workspace A and created a session for workspace B; only B was cleaned and marked.
  • A custom directory with only trusted workspace retention was skipped without a marker; system-owned retention cleaned and marked the same directory.
  • Two processes contending over 50,000 cutoff-day files produced one scan and one marker; the second process reported skipping, lock held, and both exited normally.
  • A short headless process cleaned part of 50,000 cutoff-day files, wrote no marker, and released the lock. A subsequent long-lived stream-json process removed the remaining files and wrote the marker while still alive.

The last case covers graceful CLI exit cleanup, not SIGKILL; abrupt termination continues to rely on stale-lock recovery. All temporary processes and directories were removed after the run.

@doudouOUC
doudouOUC marked this pull request as ready for review August 11, 2026 02:40
@doudouOUC
doudouOUC enabled auto-merge August 11, 2026 02:40
@doudouOUC doudouOUC self-assigned this Aug 11, 2026
@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the follow-up — gate check below. Re-run after the two feedback-batch commits (23ab89a1ba, f43734645f); both are test-only (+141 lines) addressing review findings, production code unchanged.

  • Template: complete, with a real reviewer test plan ✓
  • Problem: observed, not theoretical. OpenAI API logs grow without bound: logs/openai has no rotation or retention (observed ~95 GB / 340k files in two months) #8860 documents ~95 GB / ~340k files accumulated in two months, and feat(cli): add background cleanup for OpenAI API logs #8862 (merged) only starts the retention pass from interactive sessions — headless, stream-json SDK, and ACP processes keep accumulating logs. This PR closes that gap, exactly as the design doc's Scope section anticipated.
  • Direction: aligned. Unbounded log growth is a real disk + sensitive-data-footprint problem for SDK/daemon-heavy users, and the fix completes an already-shipped feature rather than adding a new direction.
  • Size: ~385 production lines (scheduler 240, gemini.tsx 72, throttledOnce 46, cleanup 23, acpAgent 2, settingsSchema 2 — description text only) vs ~713 test lines, plus docs and the regenerated companion schema. Below the maintainer-awareness threshold.
  • Approach: feels right. It reuses the existing cleaner, marker, and lock from feat(cli): add background cleanup for OpenAI API logs #8862 instead of forking a second mechanism; the new pieces (FIFO worker, abort-aware scan, result-enum from runThrottledOnce, 250 ms exit wait) are what the non-interactive lifecycle genuinely needs. No unrelated changes spotted — every file maps to the stated goal.
  • Risk: packages/cli/src/acp-integration/ matches this repo's revert-correlated high-risk paths, so this gets the full review depth (diagram + file map + CI evidence below). Worth noting the touch there is two lines: an import and one registration call after successful workspace-session config init.

Moving on to code review. 🔍

中文说明

感谢这个后续 PR,以下是准入检查结果。本次为 re-run:此前审查后新增的两个 commit(23ab89a1baf43734645f)均为纯测试改动(+141 行),用于响应评审意见,生产代码未变。

  • 模板:完整,且包含真实的评审者测试计划 ✓
  • 问题:已观测到,非理论性问题。OpenAI API logs grow without bound: logs/openai has no rotation or retention (observed ~95 GB / 340k files in two months) #8860 记录了两个月累积约 95 GB / 34 万个文件,而 feat(cli): add background cleanup for OpenAI API logs #8862(已合并)只从交互式会话启动保留清理——headless、stream-json SDK 和 ACP 进程仍会持续累积日志。本 PR 正是补上这个缺口,与设计文档 Scope 一节的预期一致。
  • 方向:对齐。无限增长的日志对 SDK/daemon 重度用户是真实的磁盘与敏感数据问题,本修复是补齐已上线功能,而非引入新方向。
  • 规模:约 385 行生产代码(scheduler 240、gemini.tsx 72、throttledOnce 46、cleanup 23、acpAgent 2、settingsSchema 2——仅描述文案),对比约 713 行测试代码,另有文档与重新生成的 companion schema。低于维护者关注阈值。
  • 方案:合理。复用了 feat(cli): add background cleanup for OpenAI API logs #8862 已有的清理器、marker 和锁,而不是另起一套机制;新增部分(FIFO worker、支持中止的扫描、runThrottledOnce 的结果枚举、250ms 退出等待)都是非交互生命周期真正需要的。未发现无关改动——每个文件都对应既定目标。
  • 风险packages/cli/src/acp-integration/ 命中本仓库与 revert 相关的高风险路径,因此按完整深度审查(下文含时序图 + 文件清单 + CI 证据)。需要说明的是该处改动只有两行:一个 import 和工作区会话配置初始化成功后的一次注册调用。

进入代码审查 🔍

Qwen Code · qwen3.8-max

Reviewed at f43734645fa94f7e50cfd3c67d157f3702456824 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Code review

Re-run focus: the two commits since the last reviewed revision are test-only — production code is byte-for-byte unchanged (verified via the compare API), so the earlier walkthrough carries over, and this pass checked whether the new tests actually pin what the review asked for. They do:

  • R1-1 (stop callback never asserted): the headless and stream-json lifecycle tests now capture the callback passed to registerCleanup, invoke it, and assert stopNonInteractiveOpenAILogHousekeeping ran — deleting the exit-time registration now fails the suite.
  • R1-2 (clamp branches uncovered): it.each drives retryAfterMs below the 1-minute floor and above the 24-hour cap, advancing fake timers to expected − 1 then +1 — both Math.max and Math.min are pinned.
  • R1-6 (vacuous dedup test): the test now flushes the queued microtask before re-asserting a single cleanup call, so the queued-duplicate path is actually observed.
  • R1-7 (unpinned resolution precedence): a new conflicting-source test sets both ContentGeneratorConfig and ModelsConfig dirs and asserts the content-generator dir wins.
  • wenshao's two findings: one test hangs fsPromises.unlink on a deferred promise, aborts once the unlink starts, and asserts cleanup has not returned until the pending unlink settles — pinning the final await Promise.all(batch), which I confirmed is present at head. The other mocks fsPromises.open to write the marker mid lock-acquisition and asserts the post-lock freshness recheck returns fresh without running the task — I confirmed tryAcquire really opens the lock via node:fs/promises.open, so the mock hits the real path.

The not-taken items carry coherent reasons that match the design doc: R1-3 (no ceiling on failure retries — a ceiling could leave cleanup permanently idle once a directory recovers), R1-4 (frozen retention per target — retention changes take effect after restart by documented contract), R1-5 (no job eviction — pruning could drop a target before its first log write).

The design itself still holds up on re-read:

  • Interrupted scans fail safe. cleanupOldOpenAILogs checks the signal per entry and reports completed: false; runThrottledOnce then releases the lock but skips the marker, so a later process resumes instead of seeing a false success.
  • Exit is bounded. The stop path aborts the active scan, drops queued work, and waits at most 250 ms — comfortably inside the exit-cleanup chain's budget. Stop is idempotent and a no-op when nothing was started.
  • The result enum is the minimal enabling change. completed / fresh(retryAfterMs) / locked / incomplete lets long-lived processes retry at the right cadence (daily / remaining interval / 1 min / 10 min). Interactive callers pass no signal, so their behavior is unchanged.
  • Resolution matches the writer. Targets resolve through the same resolveOpenAILogDir the logger uses, with the ModelsConfig fallback mirroring the writer's openAILoggingDir source. Deletion remains restricted to exact writer-format filenames, so a mis-resolved dir is a no-op scan, never data loss.
  • ACP wiring is minimal. Two lines in a revert-prone area: registration after successful workspace-session config init, workspace-scoped, deduped by resolved absolute directory; one process-local FIFO worker serializes scans and retry timers are unref'd.
  • Exceptional exits run the cleanup chain too (try/finally around all three run paths) — now asserted by the lifecycle tests for both success and failure.

One standing watch-item: incomplete results get no reschedule, which is correct today because incompleteness only arises from the stop-time abort — if a future change adds another abort source, that job would sit idle until re-registered.

sequenceDiagram
    participant P1 as CLI or ACP lifecycle
    participant P2 as FIFO queue (one worker)
    participant P3 as runThrottledOnce
    participant P4 as cleanupOldOpenAILogs
    P1->>P2: start with resolved log dir (dedup)
    P2->>P3: one directory at a time
    P3->>P3: check marker freshness, then lock
    P3->>P4: scan with AbortSignal
    P4-->>P3: removed, errors, completed
    P3-->>P2: completed, fresh, locked or incomplete
    P2->>P2: reschedule (daily, remaining interval, 1 min or 10 min)
    P1->>P2: exit (abort active scan, drop queue, wait up to 250 ms)
Loading
Files changed (14)
File What changed
packages/cli/src/utils/housekeeping/scheduler.ts Core of the PR: process-local FIFO queue with per-directory dedup, retry cadence per result status, graceful stop with 250 ms cap, shared cleanup helper, and a ModelsConfig fallback for the logging dir
packages/cli/src/utils/housekeeping/throttledOnce.ts Returns a discriminated result instead of a boolean; tasks can report incompleteness (lock released, marker skipped); fresh reports remaining interval
packages/cli/src/utils/housekeeping/cleanup.ts OpenAI log cleaner gains an optional AbortSignal checked per directory entry and reports whether it reached the end
packages/cli/src/gemini.tsx Non-interactive and ACP modes import the scheduler lazily, register stop as exit cleanup, start housekeeping before headless and stream-json runs, and run the cleanup chain on failure paths too
packages/cli/src/acp-integration/acpAgent.ts Two lines: registers a cleanup target after each successfully initialized workspace session config
packages/cli/src/config/settingsSchema.ts Description-only update of the retention setting text
packages/vscode-ide-companion/schemas/settings.schema.json Regenerated schema description matching the setting text change
packages/cli/src/utils/housekeeping/non-interactive-scheduler.test.ts New suite: dedup (incl. queued-duplicate path), FIFO serialization, ModelsConfig fallback, content-generator precedence, retry timing with clamp boundaries, abort on stop, 250 ms cap, post-stop rejection
packages/cli/src/utils/housekeeping/throttledOnce.test.ts Covers the result enum: fresh remaining interval, lock held, incomplete marker semantics, benign marker-write failure, and the post-lock marker recheck
packages/cli/src/utils/housekeeping/cleanup.test.ts Cancellation coverage: pre-aborted early return, per-entry abort check, and a deferred-unlink proof that the partial batch settles before returning
packages/cli/src/gemini.test.tsx Lifecycle tests asserting housekeeping start, registered stop callback, and exit cleanup on both success and failure for headless, stream-json, and ACP
packages/cli/src/acp-integration/acpAgent.test.ts Asserts registration is called with the workspace session config
docs/design/openai-log-retention.md Design doc extended: non-interactive queue, best-effort short-process semantics, expanded scope
docs/users/configuration/settings.md Retention setting description updated to cover non-interactive passes

Test evidence (PR's own CI — this is an unattended run; no PR code was executed here)

Both pull_request workflow runs on the reviewed commit completed green. The macOS/Windows unit jobs and the CLI integration job show skipped by design: ci.yml gates them to merge_group (the merge queue runs them before merge; ubuntu is the PR-time signal). The ubuntu job is not tests-only — it runs ESLint, Prettier, the settings-schema freshness check, and the full vitest suite, so lint/format/schema-drift gates are covered by the green below. Author's local black-box results (macOS only) are their claim, not independently re-run here.

Check Conclusion
Qwen Code CI / Classify PR ✅ success
Qwen Code CI / Test (ubuntu-latest, Node 22.x) — lint, format, schema check, full unit suite ✅ success
Qwen Code CI / Desktop Shell (ubuntu-22.04) ✅ success
Qwen Code CI / Desktop Shell (windows-2022) ✅ success
Qwen Code CI / web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success
Qwen Code CI / Test (macos-latest, Node 22.x) ⏭️ skipped (merge-queue-only job)
Qwen Code CI / Test (windows-latest, Node 22.x) ⏭️ skipped (merge-queue-only job)
Qwen Code CI / Integration Tests (CLI, No Sandbox) ⏭️ skipped (merge-queue-only job)
SDK Java / ubuntu-latest (Java 11, 17, 21) ✅ success
SDK Java / macos-latest (Java 21) ✅ success
SDK Java / windows-latest (Java 21) ✅ success
SDK Java / Real daemon E2E (Java 11) ✅ success
precheck-pr / precheck ✅ success

Sandboxed verification of the remaining gap is in flight: the sponsored @qwen-code /verify run (watch live) will settle whether a real headless / stream-json / ACP session resolves its log directory, completes a marker-writing scan end-to-end, and leaves no marker when interrupted — the unit suite still covers that wiring mock-on-both-sides, and the author tested macOS only. The report lands in the verification thread; read it with the same skepticism as the fork's own CI logs. (/tmux is unavailable for this PR — nothing TUI-visible and the author cannot gate it.)

中文说明

代码审查

Re-run 关注点:上次审查后的两个 commit 均为纯测试改动——生产代码逐字节未变(已用 compare API 核实),因此此前的走查结论延续有效,本次重点核对新测试是否真正钉住了评审要求。结论是钉住了:

  • R1-1(停止回调无断言):headless 与 stream-json 生命周期测试现在会捕获传给 registerCleanup 的回调、调用它并断言 stopNonInteractiveOpenAILogHousekeeping 被执行——删除退出时的注册现在会让测试失败。
  • R1-2(clamp 分支无覆盖)it.each 分别注入低于 1 分钟下限与高于 24 小时上限的 retryAfterMs,用假定时器推进到预期值 −1 再 +1——Math.maxMath.min 均被钉住。
  • R1-6(去重测试空转):测试现在先冲刷排队的微任务再复断言清理只调用一次,真正观测到了排队重复路径。
  • R1-7(解析优先级未钉住):新增冲突源测试同时设置 ContentGeneratorConfig 与 ModelsConfig 目录,断言 content-generator 目录胜出。
  • wenshao 的两条意见:一条测试用延迟 promise 挂起 fsPromises.unlink、在 unlink 开始后中止,并断言清理在挂起的 unlink 落定前不会返回——钉住了末尾的 await Promise.all(batch)(已确认 head 上存在该 await)。另一条 mock fsPromises.open,在获取锁的过程中写入 marker,断言锁后的新鲜度复查返回 fresh 且不执行任务——已确认 tryAcquire 确实通过 node:fs/promises.open 开锁,mock 命中的是真实路径。

未采纳项的理由与设计文档一致、成立:R1-3(失败重试无上限——设上限可能在目录恢复可用后让清理永久闲置)、R1-4(目标冻结保留期——按文档约定保留期变更重启后生效)、R1-5(任务不逐出——逐出可能在首次日志写入前丢掉有效目标)。

设计本身复读后依然成立:

  • 中断扫描安全失败:清理器逐条目检查信号并报告未完成;runThrottledOnce 释放锁但跳过 marker,后续进程可继续清理而不是看到假成功标记。
  • 退出有界:停止路径中止当前扫描、丢弃排队任务、最多等待 250ms,远在退出清理预算之内;停止幂等,且在清理从未启动时为空操作。
  • 结果枚举是最小的使能改动:completed/fresh(retryAfterMs)/locked/incomplete 让长驻进程按正确节奏重试;交互式调用方不传信号,行为不变。
  • 目录解析与写入方一致:清理目标经过与日志写入方相同的解析路径,ModelsConfig 回退对应写入方的目录来源;删除仍严格限定 writer 格式文件名,解析偏差最多是空扫描,不会误删。
  • ACP 接入极小:高风险区域只加两行——工作区会话配置成功初始化后注册、工作区作用域、按绝对目录去重;进程内单个 FIFO worker 串行扫描,重试定时器均已 unref。
  • 异常退出也会运行清理链(三条运行路径均 try/finally 包裹)——现在成功与失败路径都有生命周期测试断言。

一个持续的关注点:incomplete 结果不重排期。当前正确(未完成只会来自停止时的中止),但若未来新增其他中止来源,该任务会闲置到下次注册。

时序图与文件清单见英文部分(14 个文件:核心队列为 scheduler.ts;throttledOnce 改为结果枚举;cleanup 支持中止;gemini.tsx 负责生命周期接线;acpAgent.ts 仅两行注册;其余为测试、文档与重新生成的 schema)。

测试证据(PR 自身 CI——本次为无人值守运行,未执行任何 PR 代码)

被审 commit 上两个 pull_request 工作流均为绿色。macOS/Windows 单元与 CLI 集成任务显示 skipped 是设计使然:ci.yml 将其限定在 merge_group(合并队列会运行,ubuntu 是 PR 阶段的信号)。ubuntu 任务并非只跑测试——包含 ESLint、Prettier、settings schema 新鲜度检查和完整 vitest 套件。作者本地黑盒验证(仅 macOS)是其自述,未在此独立复现。

剩余缺口的沙盒验证正在进行:赞助的 @qwen-code /verify 运行将验证真实 headless/stream-json/ACP 会话确实能解析日志目录、端到端完成带 marker 的扫描,且中断扫描不留下 marker——单测对该接线仍是"双侧 mock"覆盖,且作者仅在 macOS 上测试。报告会发布在验证评论区;请以对待 fork CI 日志的同样审慎态度阅读。(/tmux 对本 PR 不适用——无 TUI 可见改动,且作者无法为其把关。)

Qwen Code · qwen3.8-max

Reviewed at f43734645fa94f7e50cfd3c67d157f3702456824 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — solid, focused follow-up; the re-run only added test strength, no new concerns surfaced.

Stepping back: this remains the second half of a fix the project already committed to, against a problem with hard observational evidence (95 GB / 340k files in #8860). What changed since the last reviewed revision is exactly what a healthy review loop should produce: two test-only commits that close real mutation holes — the exit-time stop registration, both retry-clamp branches, the queued-duplicate dedup path, the content-generator precedence, the partial-batch settling, and the post-lock marker recheck. I spot-checked the two load-bearing premises of those tests at head (the final await Promise.all(batch) in the cleaner; tryAcquire locking via node:fs/promises.open) and both hold. The three not-taken findings have documented, defensible reasons that match the design doc rather than convenience.

The one substantive reservation is unchanged: the end-to-end wiring (a real session deleting files and writing the marker) is still only covered mock-on-both-sides in unit tests plus the author's macOS black-box run. That is what the in-flight sponsored @qwen-code /verify run is settling — its report will land in the verification thread and is worth reading before merge. It does not block this approval: the production code under test is byte-for-byte what was reviewed, green in CI including lint/format/schema-drift gates, and a maintainer has already approved this head. My earlier approval was dismissed by the new pushes, so this one is re-issued pinned to the reviewed commit.

中文说明

置信度:4/5 —— 扎实、聚焦的后续 PR;re-run 只增强了测试,没有出现新的疑虑。

退一步看:这仍是项目已承诺修复的后半段,针对的是有硬观测证据的问题(#8860 中 95 GB / 34 万文件)。相比上次审查的版本,变化恰恰是健康评审循环应有的产物:两个纯测试 commit 补上了真实的变异漏洞——退出时的停止注册、重试 clamp 的两个边界、排队重复的去重路径、content-generator 优先级、部分批次的落定、以及获锁后的 marker 复查。我在 head 上抽查了这些测试依赖的两个关键前提(清理器末尾的 await Promise.all(batch)tryAcquire 通过 node:fs/promises.open 开锁),两者均成立。三条未采纳的意见都有与设计文档一致的、站得住脚的理由,而非图省事。

唯一实质性的保留意见未变:端到端接线(真实会话删除文件并写入 marker)在单测中仍是"双侧 mock"覆盖,外加作者的 macOS 黑盒验证。正在进行中的赞助 @qwen-code /verify 运行将补上这一环——报告会发布在验证评论区,合并前值得一读。它不阻塞本次批准:被测的生产代码与所审查的逐字节一致,CI 绿色(含 lint/格式/schema 漂移门禁),且已有 maintainer 批准了当前 head。我此前的批准已被新推送dismiss,现重新签发并钉在所审查的 commit 上。

Qwen Code · qwen3.8-max

Reviewed at f43734645fa94f7e50cfd3c67d157f3702456824 · re-run with @qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, looks ready to ship. ✅

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed. Suggestions are inline.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally (macOS/Windows unit-matrix jobs also skipped; unit suites ran locally on Linux only).

Not explored to full depth (tool budget reached): You are review agent reverse-audit — Reverse audit agen...: none — all checks I started were completed within budget.; This PR (#8893) extends OpenAI API log retention cleanup ...: none — all checks in my dimension completed within budget.; You are review agent reverse-audit — Reverse audit agen...: none — all checks above completed within budget.; You are review agent reverse-audit — Reverse audit agen...: none — all checks above were completed within budget.; chunk 2: none — all checks I identified were completed within budget., and 3 more.

Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.

Test Plan (not a blocker): 582 assertions should pass — this review observed 481 passed.

中文说明

已审查。 建议见行内评论。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally (macOS/Windows unit-matrix jobs also skipped; unit suites ran locally on Linux only)。

未探索到全部深度(达到工具调用预算):You are review agent reverse-audit — Reverse audit agen...:none — all checks I started were completed within budget.;This PR (#8893) extends OpenAI API log retention cleanup ...:none — all checks in my dimension completed within budget.;You are review agent reverse-audit — Reverse audit agen...:none — all checks above completed within budget.;You are review agent reverse-audit — Reverse audit agen...:none — all checks above were completed within budget.;chunk 2:none — all checks I identified were completed within budget.,另有 3 条。

未审查:反向审计——在 5 轮的反审轮数上限内未收敛。

Test Plan(非阻断):582 assertions should pass — this review observed 481 passed

— qwen3.8-max via Qwen Code /review (v0.21.9)

Comment thread packages/cli/src/gemini.tsx
Comment thread packages/cli/src/utils/housekeeping/scheduler.ts
Comment thread packages/cli/src/utils/housekeeping/scheduler.ts
Comment thread packages/cli/src/utils/housekeeping/scheduler.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts
Comment thread packages/cli/src/utils/housekeeping/scheduler.ts

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed. Suggestions are inline.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

Not reviewed: build-and-test — the full packages/cli test command timed out and packages/vscode-ide-companion tests did not run.

中文说明

已审查。 建议见行内评论。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未审查:build-and-test — the full packages/cli test command timed out and packages/vscode-ide-companion tests did not run。

— gpt-5.6-sol via Qwen Code /review (v0.21.9)

Comment thread packages/cli/src/utils/housekeeping/cleanup.test.ts
Comment thread packages/cli/src/utils/housekeeping/throttledOnce.ts
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Review feedback batch (23ab89a)

Item Action
R1-1 Fixed: asserted the registered headless and stream-json stop callbacks.
R1-2 Fixed: covered both fresh-retry clamp boundaries.
R1-3 Not taken: the documented ten-minute retry preserves automatic recovery; a ceiling could leave cleanup permanently idle.
R1-4 Not taken: retention changes explicitly require restart, so partial ACP-only hot reload would violate the documented contract.
R1-5 Not taken: process-lifetime targets are intentional; pruning a missing directory can drop the target before the first log write, and an arbitrary cap can discard active workspaces.
R1-6 Fixed: made the dedup test observe the queued duplicate path.
R1-7 Fixed: pinned ContentGeneratorConfig precedence over ModelsConfig.

Verification: npm run build; npm run typecheck; 88 focused Vitest tests; focused Prettier and ESLint checks.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Review feedback batch (f437346)

Item Action
Partial deletion batch on cancellation Fixed: retained dense nonmatching-entry cancellation coverage and added a deferred filesystem operation proving cleanup waits before returning incomplete.
Post-lock marker recheck Fixed: added a deterministic acquire-after-completion case that returns fresh and does not run the task.

Verification: npm run build; npm run typecheck; 126 focused Vitest tests; focused Prettier and ESLint checks.

@yiliang114 yiliang114 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. The deletion predicate is unchanged and byte-for-byte the #8862 writer-contract regex — unparseable names skipped (never mtime-deleted), boundary day strictly-older via mtime, and headless/stream-json/ACP all funnel through the same cleanupOldOpenAILogs with no broader predicate anywhere; the cross-package test generates a real OpenAILogger filename and asserts deletion. Lock/marker/cancellation semantics are correct: the lock always releases in finally, the marker is written only on completion, a cancelled scan returns completed:false with no marker so a later long-lived process resumes, abrupt death is covered by the 1-hour stale-lock takeover, and in-flight unlink batches settle before the lock releases. The FIFO worker serializes per-dir jobs with dedup and a bounded stop drain. All nine prior suggestions are resolved (six fixed with tests, three declined with rationale consistent with the documented contract). CI green on head. Nothing blocks merge.

@doudouOUC

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run

Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check.

Scripted assertions: 795 passed · 0 failed · 795 total

中文 — 判定:✅ 通过 · 可合入(agent 判定)

沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查

脚本断言:795 通过 · 0 失败 · 795 总计

Verification report

PR 8893 Deep Verification — feat(cli): clean up OpenAI logs in non-interactive sessions

Verdict: merge-ready — scripted assertions: 795 pass / 0 fail (ledger notes below). Verified head: f43734645fa94f7e50cfd3c67d157f3702456824 (merge commit 50722d8223, base tip 3f6551f9cf). First round; no previous-report.md.

中文摘要

结论:merge-ready(可合并)。795 项脚本化断言全部通过,0 失败。

  • A/B 结论:中心声明成立。同场景一次性 headless 运行:head 构建删除了全部 3 个过期的 writer 格式日志、保留近期文件与形似文件(openai-my-export.json 等)、写入唯一完成 marker 并释放锁;base 构建在完全相同的模型流量下(mock server 均收到 2 次请求)一个文件都未删除、无 marker —— 非交互清理能力完全由本 PR 引入(见 01-ab-head-vs-base.png)。
  • 中断/恢复:20 万文件目录中,短进程退出时中止扫描 —— 不写 marker、释放锁、保留 198,368 个残留文件;后续进程恢复扫描、清空残留并写入唯一 marker(02-interrupt-resume.png)。把 throttledOnce!== false 契约突变回旧行为后,错误 marker 复现且后续进程被该 marker 抑制、198,400 个文件永久残留 —— 证明该 hunk 承重(06-mutation-contract-killed.png)。
  • 所有权与生命周期:workspace 级 retention + 自定义目录 → 跳过(无删除、无 marker);追加 system 级 retention → 同一目录被清理并标记;无输入(exit 1)与认证失败均不启动清理(03-ownership-lifecycle.png)。锁竞争下第二个进程干净退避,全程恰好一次扫描、一个 marker(05-lock-contention.png)。stream-json transport 同样完成清理并写 marker(04-stream-json-transport.png)。
  • 测试与变异:聚焦套件 547 项全绿(housekeeping 全部 + gemini + acpAgent);对 cleanup.ts 的逐条目中止检查做细粒度变异,其自身新增测试以行为断言(expected vs actual)将其杀死,恢复后 27/27 复绿(07-unit-mutation-killed.png)。
  • Findings:无阻塞项。唯一非绿:settings.test.ts 中 12 个与本 PR 无关的失败,已证明将本 PR 唯一改动的 settingsSchema.ts 回退到 base 后失败集合逐字节相同,属容器环境既有问题,不计入 PR 账目。
  • 未覆盖:ACP 真实 E2E(仅单元级钉住)、交互式路径 E2E(10 分钟延迟)、Windows、systemDefaults 回退分支、逐 commit 归因(depth-2 shallow)。

Scope

Central claim: one-shot headless invocations now run OpenAI log retention cleanup — expired writer-format files removed, lookalikes preserved, one completion marker, lock released — where base does nothing.

Secondary claims: (1) an interrupted short process releases the lock without a false marker and a later process resumes to completion; (2) ownership/lifecycle ordering — workspace-only retention on a custom dir skips; cleanup starts only after auth success and (one-shot) after non-empty input; concurrent processes on one dir produce exactly one completed scan.

Central claim — A/B table

Identical scenario per arm: scratch HOME/QWEN_HOME, user settings {openAILoggingDir: <abs>, openAILogRetentionDays: 7}, seeded dir = 3 expired writer-format files (dates 39–41 days back), 1 recent writer file, 1 expired lookalike openai-my-export.json, 1 expired non-JSON, 1 directory shaped like a writer filename. CLI: --auth-type openai --openai-base-url http://127.0.0.1:<port>/v1 --openai-api-key sk-mock -p … against a loopback mock OpenAI server. Base build = HEAD^1 worktree, tsc --build --noCheck + the repo's copy_files.js + regenerated git-commit.ts; shares root node_modules — a clean control because the diff touches no packages/core, packages/sdk, package.json, or lockfile (asserted via git diff HEAD^1..HEAD --stat on those paths: empty), and the workspace link realpath was checked (node_modules/@qwen-code/qwen-code-core → head-tree packages/core, byte-identical between arms).

Cell Build Model traffic Expired removed Recent / lookalike / non-JSON / dir Marker Lock Exit
head packages/cli/dist @​ f4373464 2 requests 3/3 all 4 kept 1 written released 0
base (control) base-tree dist @​ 3f6551f9 2 requests 0/3 all 4 kept (byte-identical dir) none none 0

9/9 head assertions, 5/5 base assertions (the base arm's expected inaction is encoded as assertions, so its predicted "failure to clean" counts as passed control cells). Witness: 01-ab-head-vs-base.png. Both arms re-run under capture exited 0 — deterministic.

The same seed shape was driven through the stream-json transport (--input-format stream-json, one {"type":"user",…} line): expired file removed, recent kept, marker written, result message emitted, exit 0 — 6/6 (04-stream-json-transport.png).

Interrupt / resume (secondary claim 1)

Calibrated against the real compiled cleaner (20,000 files in 147 ms ⇒ N = 200,000). Phase 1 (mock delay 0): process lived 1.4 s, removed 1,632 files, was aborted by its own exit cleanup — no marker, no lock, 198,368 residue, exit 0, debug log observed completed=false. Phase 2 (mock delay 30 s): the resuming process removed all residue, kept both recent files, wrote the single marker, exit 0. 13/13 (02-interrupt-resume.png).

Load-bearing proof (mutation A/B). The compiled throttledOnce.js in a scratch dist copy was patched from taskCompleted = (await task()) !== false; back to the pre-PR await task(); taskCompleted = true; (nothing else changed). Re-running the identical harness against the mutant:

Build Phase 1 marker Phase 1 residue Phase 2 outcome
head none 198,368 residue removed, marker written
contract mutant false marker written 198,400 fresh marker suppresses the scan — all residue kept

The mutant reproduces exactly the bug this PR exists to fix (false success marker suppressing a later complete scan). Witness: 06-mutation-contract-killed.png (captured command exits 1 = mutant killed).

Ownership + lifecycle ordering (secondary claim 2) — 13/13

Witness: 03-ownership-lifecycle.png.

Cell Setup Outcome
A custom dir + workspace-only retention (trusted by default) run ok; file not removed; no marker; debug log: "workspace-scoped openAILogRetentionDays is unsafe … skipping"
B same dir + system retention via QWEN_CODE_SYSTEM_SETTINGS_PATH same dir cleaned; marker written
C one-shot, no input exit 1, No input provided…; nothing removed; no marker
D --auth-type openai, no key exit 1, Missing API key…; nothing removed; no marker

Cells C/D confirm cleanup starts only after auth and non-empty input (housekeeping never ran — no marker, no deletions, no mock traffic).

Lock contention — 9/9

Process A (400,001 seeded files, slow mock) held the directory lock while scanning; process B started inside that window: exit 0, no marker, cleaned nothing (A's lock asserted held at B's exit — only the lock holder can delete), then A completed the single scan, released the lock, wrote the single marker (count over QWEN_HOME asserted = 1), and the marker survived SIGKILL of A post-completion. Witness: 05-lock-contention.png. (An earlier run of this harness carried two oracle bugs of mine — attributing A's in-flight deletions to B, and counting markers in the log dir instead of QWEN_HOME; both fixed, rerun green.)

Targeted gates

  • Focused suites (src/utils/housekeeping/, gemini.test.tsx, acpAgent.test.ts): 547 passed / 0 failed across 6 files (08-focused-unit-gate.png), including the PR's new 368-line non-interactive scheduler suite.
  • Settings suites (settings.test.ts, settingsSchema.test.ts): 189 passed, 12 failed — attributed pre-existing, not PR-caused: the failing names are byte-identical when the PR's only settings-related change (settingsSchema.ts, a description string) is reverted to HEAD^1 (diff of sorted failure lists empty; tree restored, git status clean). They cover .env interpolation / home-dir scoping, are sensitive to this container's environment, and are untouched by the PR; they are excluded from the assertion ledger and named here rather than silently dropped. A direct base-tree vitest run was not possible (vite transform of core imports fails from a worktree in this container — recorded, not a PR signal).
  • Generated artifact check: re-running scripts/generate-settings-schema.ts reproduces the committed vscode-ide-companion/schemas/settings.schema.json byte-for-byte (git status clean after regeneration).
  • Build/typecheck: npm run build at HEAD completed before this round (environment contract) — it typechecks as part of tsc --build.

Vacuity / mutation matrix

Guard introduced by PR Oracle suite Pinned?
taskCompleted = (await task()) !== false (no false marker) E2E interrupt/resume harness killed — mutant M1 (compiled-dist patch) reproduces false marker + suppressed resume (06-mutation-contract-killed.png)
Per-entry abort check in cleanupOldOpenAILogs cleanup.test.ts killed — fine-grained source mutation fails 2 of the PR's new tests with expected-vs-actual assertions (expect(r.completed).toBe(false) → received true; plus the batch-settle test); restore ⇒ 27/27 green (07-unit-mutation-killed.png)
Pre-entry abort check (signal.aborted before opendir) cleanup.test.ts "already aborted" test pinned by the suite (not independently mutated — its sibling above was the fine-grained probe)
Result statuses + retry cadences (locked 1 min / fresh clamp / failure 10 min / completed 24 h / 250 ms stop grace) fake-timer unit tests in the new scheduler suite pinned by suite; cadences match observed E2E behavior; not mutated
Dedup by resolved logDir / FIFO serialization new scheduler suite (mocked cleaner) pinned by suite; dedup also implied by single-marker observations in every E2E cell

Positive controls: the head arm of the A/B (cleanup demonstrably happens) and the mock-server request counts (requestsSeenByMock ≥ 1 in every CLI cell) prove the scenarios reached the code under test. No mutant survived; no vacuous test found.

Findings

None. No blocking, no non-blocking defects produced by this round. (The 12 settings-suite failures are reported under Targeted gates as attributed pre-existing container behavior, not as PR findings.)

Not covered

  • ACP end-to-end (real initialize/session/new over the ACP wire): wiring is unit-pinned (acpAgent.test.ts asserts startNonInteractiveOpenAILogHousekeeping is called with the initialized session config; 395 acpAgent tests green), and the shared queue/marker/lock machinery is proven by the headless + stream-json E2E cells — but a live ACP daemon run was not driven in this round.
  • Interactive housekeeping path E2E (10-minute first-pass delay makes it impractical here); it shares runOpenAILogCleanup with the proven non-interactive path, and runThrottledOnce's only other consumers discard the new result object (verified by grep — no boolean-return consumers remain).
  • Windows (author marked it untested; this round ran on Linux, which the author also had not tested locally — now has container evidence).
  • systemDefaults retention fallback branch (system ?? user ?? systemDefaults ?? DEFAULT): not exercised by an E2E cell; covered by the resolution order in code review only.
  • Per-commit attribution: the checkout is depth-2 (shallow), git rev-list HEAD^1..HEAD^2 sees 1 of the 3 metadata commits, so the aggregate HEAD^1..HEAD diff was verified, not individual commits.
  • Grace-window lock-leak edge: if exit kills the process before the aborted scan's finally unlinks the lock, recovery relies on the existing 1-hour stale-lock takeover (documented by the PR). My phase-1 cell observed the lock released within the grace window; the leak edge itself was not staged.
  • No untrusted-text scaling ladder was run: the filename regex is pre-existing (feat(cli): add background cleanup for OpenAI API logs #8862) and runs over filenames the CLI itself wrote, not outsider-authored text.

Methodology

One node:22-bookworm CI container (Node v22.23.2), merge-ref checkout at depth 2. All E2E harnesses (kept as .mjs in this artifact dir, rerunnable) drove the compiled packages/cli/dist/index.js (head) and a base-worktree build (HEAD^1, tsc --build --noCheck + repo copy_files.js + regenerated git-commit.ts) as real child processes against a loopback mock OpenAI chat-completions server derived from the repo's e2e skill template, with per-cell HOME/QWEN_HOME isolation and request counting at the server. Oracles were filesystem state (seeded writer-format files, exact marker name computed as .openai-logs-cleanup-<sha256(logDir)16>), lock presence, exit codes, stderr, and mock request logs — every check a scripted PASS/FAIL line. Mutation rounds patched a scratch dist copy (M1) and the live source with sha256-verified backup/restore (U1). Raw per-run logs are in logs/; evidence captures (each a full re-run of the harness under scripts/verify-capture.mjs, all reproducing the original results) are the 8 PNGs referenced above. Base worktree removed after cell capture.

Evidence images

01-ab-head-vs-base

02-interrupt-resume

03-ownership-lifecycle

04-stream-json-transport

05-lock-contention

06-mutation-contract-killed

07-unit-mutation-killed

08-focused-unit-gate

Harness scripts and raw logs are in the workflow run artifacts (7-day retention).

Qwen Code · sandboxed verification

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, looks ready to ship. ✅

@doudouOUC
doudouOUC added this pull request to the merge queue Aug 11, 2026
Merged via the queue into QwenLM:main with commit 60c1825 Aug 11, 2026
106 of 108 checks passed
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.21.10.

@yiliang114

Copy link
Copy Markdown
Collaborator

PR 已合并(merged)并已通过评审(APPROVED by yiliang114, qwen-code-ci-bot),CI 为绿色。以下是我对变更的评审总结:

Scope: 将 OpenAI API 日志保留从交互式 housekeeping 扩展到 headless、stream-json SDK 和 ACP daemon 会话。14 个文件,+1028/-86 行。

Key design observations:

  • scheduler.ts — FIFO worker 模式,nonInteractiveJobs 按 logDir 去重,drainNonInteractiveQueue 串行执行。runOpenAILogCleanupcleanupOldOpenAILogscompleted 状态正确映射到 runThrottledOnce 的返回值,调度器据此选择 completed/fresh/locked/incomplete 重试策略。取消路径通过 AbortController 实现,退出时 stopNonInteractiveOpenAILogHousekeeping 等待 worker 最多 250ms。

  • throttledOnce.ts — 返回类型从 boolean 演进为 ThrottledOnceResult 联合类型,让调用方无需猜测重试间隔。锁获取后重新检查 marker 以关闭 TOCTOU 窗口,是一个好的实践。

  • cleanup.ts — 删除谓词与 feat(cli): add background cleanup for OpenAI API logs #8862 的 writer-contract regex 逐字节相同,AbortSignal 支持协作式取消,completed: false 时跳过 marker 写入,使后续长驻进程可以继续扫描。

  • gemini.tsx — 生命周期集成精简:仅在非交互路径上懒加载 scheduler,registerCleanup 注册 stop,try/finally 确保异常路径也执行 runExitCleanup

  • Tests — 368 行新 non-interactive-scheduler.test.ts 覆盖去重、worker 串行化、定时器重试、取消/停止行为;gemini.test.tsx 覆盖 headless、stream-json、ACP 路径的 housekeeping 启动和清理注册。

No blockers. 所有先前的评审建议均已解决(6 个已修复+测试,3 个已保留并有合理理由)。CI 在 head 上为绿色。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review/self-reported The linked issue was opened by the PR author (self-reported)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpenAI API logs grow without bound: logs/openai has no rotation or retention (observed ~95 GB / 340k files in two months)

4 participants