Skip to content

feat(daemon): add session artifact content retention#6346

Closed
chiga0 wants to merge 16 commits into
QwenLM:feat/session-artifacts-persistence-v2-designfrom
chiga0:feat/session-artifacts-content-retention
Closed

feat(daemon): add session artifact content retention#6346
chiga0 wants to merge 16 commits into
QwenLM:feat/session-artifacts-persistence-v2-designfrom
chiga0:feat/session-artifacts-content-retention

Conversation

@chiga0

@chiga0 chiga0 commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

This is the content-retention layer stacked on #6259. It adds pinned artifact content retention on top of the metadata and restart-recovery foundation: retained content can be pinned and unpinned, read through daemon APIs, addressed by content references and hashes, and maintained with quota and cleanup behavior.

Why it's needed

The base PR keeps artifact metadata durable across daemon restart without committing to long-lived file content. This follow-up isolates the heavier review surface so reviewers can focus on filesystem safety, retained-content API behavior, quota enforcement, cleanup behavior, and REST/ACP/SDK compatibility without mixing it with metadata rebuild semantics.

Reviewer Test Plan

How to verify

Review this PR as a stacked diff against feat/session-artifacts-persistence-v2-design, not against main. Confirm that metadata persistence remains in #6259 and this PR only adds retained-content behavior. Verify pin/unpin behavior, retained content reads, quota handling, cleanup behavior, and client/API exposure.

Evidence (Before & After)

N/A for UI. Local validation passed with:

  • cd packages/acp-bridge && npm run typecheck
  • cd packages/sdk-typescript && npm run typecheck
  • cd packages/acp-bridge && npm run build
  • cd packages/cli && npm run typecheck
  • cd packages/sdk-typescript && npx vitest run test/unit/DaemonClient.test.ts test/unit/DaemonSessionClient.test.ts test/unit/acpRouteTable.test.ts
  • cd packages/cli && npx vitest run src/serve/server.test.ts -t "session artifact routes" --coverage.enabled=false
  • cd packages/cli && npx vitest run src/serve/acp-http/transport.test.ts -t "_qwen/session/artifacts" --coverage.enabled=false
  • cd packages/acp-bridge && npx vitest run src/bridge.test.ts src/sessionArtifacts.test.ts
  • npx prettier --check packages/acp-bridge/src/bridge.test.ts
  • git diff --check

Tested on

OS Status
macOS tested
Windows not tested
Linux not tested

Environment (optional)

Local package typecheck/build/vitest validation on the stacked branch.

Risk & Scope

  • Main risk or tradeoff: retained content expands the filesystem and API surface, especially around quota, cleanup, hashes, and retained-content reads.
  • Not validated / out of scope: manual Windows/Linux local runs; CI should cover cross-platform behavior.
  • Breaking changes / migration notes: no intended breaking changes; this PR should merge only after feat(daemon): persist session artifacts across restarts #6259.

Linked Issues

Stacked on #6259.

中文说明

What this PR does

这是堆叠在 #6259 之上的 content-retention 层。它在 metadata 和 daemon restart recovery 基础上增加被 pin 的 artifact content 保留能力:内容可以 pin/unpin,可以通过 daemon API 读取,可以用 content reference/hash 定位,并且有 quota 和清理行为。

Why it's needed

基础 PR 只保证 artifact metadata 在 daemon 重启后可恢复,不承诺长期保存文件内容。这个后续 PR 把更重的 review 面单独隔离出来,让 reviewer 可以专注检查文件系统安全、retained-content API 行为、quota enforcement、清理行为以及 REST/ACP/SDK 兼容性,而不用和 metadata rebuild 语义混在一起。

Reviewer Test Plan

How to verify

请按相对 feat/session-artifacts-persistence-v2-design 的 stacked diff review,而不是按相对 main 的完整 diff review。确认 metadata persistence 仍在 #6259 中,本 PR 只增加 retained-content 行为。重点验证 pin/unpin、retained content read、quota、清理行为,以及 client/API 暴露。

Evidence (Before & After)

UI 不适用。本地验证已通过上面列出的 typecheck、build、vitest、prettier 和 diff check 命令。

Tested on

macOS 已本地验证;Windows/Linux 未本地验证,依赖 CI 覆盖。

Environment (optional)

在 stacked branch 上运行本地 package typecheck/build/vitest 验证。

Risk & Scope

  • 主要风险或取舍:retained content 扩大了文件系统和 API surface,尤其是 quota、清理、hash、retained-content read 等边界。
  • 未验证 / 不在范围内:未手动跑 Windows/Linux 本地验证,跨平台行为交给 CI。
  • Breaking changes / migration notes:无预期 breaking change;本 PR 应在 feat(daemon): persist session artifacts across restarts #6259 之后合并。

Linked Issues

堆叠在 #6259 之上。

@chiga0

chiga0 commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator Author

This is the corrected upstream stacked PR for content retention. It is stacked on #6259 and should be reviewed as the delta from feat/session-artifacts-persistence-v2-design to chiga0:feat/session-artifacts-content-retention.

The fork-only PR chiga0#3 was opened in the wrong repository and is superseded by this PR. Local validation before opening this PR passed: acp-bridge/sdk/cli typecheck, acp-bridge build, targeted SDK/CLI/ACP/acp-bridge vitest runs, prettier check, and git diff --check.

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓ — all required sections present, bilingual, reviewer test plan included.

Problem: This is a feature addition, not a bug fix — the "problem existence" check doesn't apply in the usual sense. The content-retention layer is a planned extension of the session artifact system described in the design doc (session-artifacts-persistence-v2-design.md). The use case is clear: users need retained workspace file content that survives workspace changes, with managed copies, quota, and cleanup.

Direction: Aligned. Session artifact content retention (pin/unpin, managed copies, fsck/gc) is a natural next step on top of the metadata persistence foundation in #6259. The roadmap/session-management label fits. No direct CHANGELOG reference yet, but the area is clearly within scope.

Approach: The scope is substantial (~1,890 lines of non-test source across three packages) but focused on a coherent feature. The content store is well-designed: atomic writes, SHA-256 integrity, workspace path traversal protection (symlink/hardlink/TOCTOU checks), quota enforcement (50MB per file, 256MB total), lease-based GC protection. Tests are comprehensive and match the source scope (~1,340 lines). The stacking on #6259 is the right call to keep reviews manageable.

One concern: Base PR #6259 currently has CHANGES_REQUESTED status. This stacked PR can be reviewed independently, but merging is blocked until the base resolves. Worth confirming the base PR's review status is being addressed in parallel.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓ — 所有必填部分齐全,双语,包含 reviewer test plan。

问题: 这是功能新增而非 bug fix,"问题存在"检查不适用常规标准。content-retention 层是 session artifact 系统的计划扩展,设计文档中有描述。用例明确:用户需要保留的 workspace 文件内容,带 managed copy、quota 和清理。

方向: 对齐。session artifact content retention(pin/unpin、managed copy、fsck/gc)是 #6259 metadata persistence 基础上的自然扩展。roadmap/session-management 标签合适。

方案: 范围较大(~1,890 行非测试源码,跨三个 package),但聚焦在同一个功能上。content store 设计良好:原子写入、SHA-256 完整性校验、workspace 路径穿越保护、quota enforcement、lease-based GC 保护。测试覆盖面广。stacked PR 方式正确。

一个关注点: 基础 PR #6259 当前状态为 CHANGES_REQUESTED。本 PR 可以独立 review,但合并需等基础 PR 解决。

进入代码审查 🔍

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Code Review

Independent proposal (before reading diff): For content retention on top of metadata persistence, I'd add a SessionArtifactContentStore class in acp-bridge that copies workspace files to a managed content directory under QWEN_HOME/session-artifacts/content/, using atomic write (temp + rename), SHA-256 integrity, quota enforcement, and session-scoped GC. New pin/unpin/fsck/gc methods on the bridge. REST routes + ACP dispatch for each. SDK client methods + types. Defense-in-depth validation at both route and bridge layers.

Comparison with PR: The implementation closely matches this proposal. The content store is well-structured — atomic writes via temp file + fsync + rename, SHA-256 hashing during copy (not a separate pass), and a manifest-based content directory layout. Security is thorough: realpath traversal check before open, O_NOFOLLOW on open, post-open stat verification (TOCTOU guard), hardlink detection, symlink escape rejection, and size limit enforcement during streaming copy.

Findings:

No critical blockers. A few observations:

  • Validation layering: ttlDays and mode validation is duplicated between routes/session.ts (HTTP layer) and bridge.ts (bridge layer). This is intentional defense-in-depth and matches the pattern used by the existing artifact add/remove routes — not a concern.

  • GC scope is session-conservative: gc() only removes content belonging to the requesting session, while scanUsedBytes() counts total across all sessions. This means other sessions' content counts against quota but can't be collected by this session's GC. This is the safe choice for a first implementation — cross-session GC would need careful reference tracking. Worth noting for future work.

  • Lease mechanism: The leaseContentRefs / releaseContentRef pattern protects content from being GC'd during concurrent reads. The enqueueWrite serialization prevents concurrent pin/GC races. Both are correct.

  • Content ID format: sha256-suffix16hex where the suffix is derived from sessionId:artifactId. This means the same file pinned by different sessions/artifacts gets different content IDs and separate storage. This is by design (session-scoped content lifecycle) but means deduplication across sessions is not implemented — acceptable for now.

Real-Scenario Testing

Not performed. This PR adds daemon API endpoints (pin/unpin/fsck/gc) that require a running qwen serve instance with an active session and workspace files. The base PR #6259 (which this is stacked on) has CHANGES_REQUESTED status, so building from the PR branch requires the base to be resolved first. The author's reported local validation covers typecheck, build, and targeted vitest runs across acp-bridge, SDK, and CLI packages.

The unit test coverage is thorough (~1,340 lines of new test code) and covers the key scenarios: pin/unpin lifecycle, TTL expiry, quota enforcement, symlink/hardlink rejection, GC behavior, fsck integrity, lease protection, concurrent pin serialization, and REST/ACP transport.

中文说明

代码审查

独立提案: 在 metadata persistence 之上添加 content retention,我会在 acp-bridge 中添加 SessionArtifactContentStore 类,使用原子写入(temp + rename)、SHA-256 完整性、quota enforcement 和 session-scoped GC。bridge 上新增 pin/unpin/fsck/gc 方法。REST 路由 + ACP dispatch。SDK 客户端方法 + 类型。路由层和 bridge 层都做验证。

与 PR 对比: 实现与提案高度一致。content store 结构良好——原子写入、copy 过程中同步 hash、基于 manifest 的目录布局。安全性全面:realpath 穿越检查、O_NOFOLLOW、open 后 stat 验证(TOCTOU 防护)、hardlink 检测、symlink 逃逸拒绝、流式 copy 中大小限制。

发现: 无关键阻塞问题。

  • 验证分层: ttlDays/mode 验证在路由层和 bridge 层重复——有意的纵深防御,与现有 artifact 路由模式一致。
  • GC 范围: gc 只清理当前 session 的 content,但 quota 统计跨所有 session。安全的第一版实现。
  • Lease 机制: 保护 content 在并发读取时不被 GC。写入序列化防止 pin/GC 竞争。
  • Content ID 格式: 不同 session/artifact 的相同文件有不同的 content ID,无跨 session 去重——当前可接受。

真实场景测试

未执行。 此 PR 添加 daemon API 端点,需要运行的 qwen serve 实例。基础 PR #6259 当前 CHANGES_REQUESTED,无法独立构建。作者的本地验证覆盖了 typecheck、build 和 vitest。单元测试覆盖面广(~1,340 行新测试代码)。

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Reflection

Stepping back: this is a well-executed stacked PR that adds a content-retention layer exactly as described in the design doc. The implementation matches what I'd propose independently — content store with atomic writes, SHA-256 integrity, quota, session-scoped GC, and comprehensive security checks against workspace path traversal attacks. The test suite is thorough and covers the important edge cases (TTL expiry, concurrent pins, symlink escape, hardlink rejection, quota overflow, lease protection).

The code doesn't feel over-engineered. Each piece serves the stated goal: the manifest format enables future fsck, the lease mechanism prevents GC-during-read races, the write queue prevents concurrent quota corruption. Nothing speculative.

What I'd flag for the maintainer:

  • Base PR feat(daemon): persist session artifacts across restarts #6259 is CHANGES_REQUESTED. This stacked PR should merge only after the base resolves. The code itself is ready, but the dependency chain isn't.
  • Cross-package scope (~1,890 lines across acp-bridge, cli, sdk-typescript) — flagged for awareness, not as a concern. The changes are coherent and focused.
  • No tmux testing performed — daemon API endpoints can't be tested without a running server, and the base PR isn't mergeable yet. Unit test coverage compensates.

The author clearly understands the codebase conventions — validation patterns match existing routes, error handling follows established patterns, capability gating is consistent with session_artifacts_persistence. If I had to maintain this in six months, I'd thank the author for the clear separation between content store, bridge logic, and transport layers.

Verdict: Code is ready to merge once #6259 lands. Escalating to the maintainer for the cross-package scope and stacked-PR dependency awareness.

中文说明

总结

这是一个执行良好的 stacked PR,按设计文档添加了 content-retention 层。实现与我的独立提案一致——content store 带原子写入、SHA-256 完整性、quota、session-scoped GC、全面的 workspace 路径穿越防护。测试覆盖面广。

代码没有过度工程化。每个部分都服务于目标:manifest 格式支持未来 fsck,lease 机制防止 GC 读竞争,写入队列防止并发 quota 损坏。无投机性代码。

需 maintainer 关注的点:

  • 基础 PR feat(daemon): persist session artifacts across restarts #6259 是 CHANGES_REQUESTED。 本 PR 应在基础 PR 解决后合并。代码本身已就绪,但依赖链未完成。
  • 跨 package 范围(~1,890 行,跨 acp-bridge、cli、sdk-typescript)——标注供了解,非问题。
  • 未执行 tmux 测试 —— daemon API 端点需要运行中的服务器,基础 PR 尚不可合并。单元测试覆盖补偿。

代码在 #6259 合并后即可 ready to ship。转交 maintainer 关注跨 package 范围和 stacked PR 依赖。

Qwen Code · qwen3.7-max

秦奇 added 2 commits July 6, 2026 00:35
@chiga0

chiga0 commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator Author

Design decision needed for content-retention review: a #6259 review thread flagged that pin() can let clients pin the full artifact set, leaving no evictable non-pinned candidates. This belongs to #6346 rather than #6259.

Please decide the intended quota policy before implementation: separate maxPinnedArtifacts, cap pinned artifacts below maxArtifacts, or another explicit quota rule. I did not resolve the original #6259 thread for this one because it changes the content-retention quota/security boundary.

@chiga0

chiga0 commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator Author

Updated the stacked content-retention branch in dc47b9deb by merging the refreshed base branch from #6259. The merge had no content-retention conflicts.

Validation run after the stack update:

  • cd packages/acp-bridge && npm run typecheck
  • cd packages/acp-bridge && npm run build
  • cd packages/sdk-typescript && npm run typecheck
  • cd packages/cli && npm run typecheck
  • cd packages/acp-bridge && npx vitest run src/sessionArtifacts.test.ts src/bridge.test.ts -t "content|Content|retain|retained|pin|unpin|artifact"
  • cd packages/cli && npx vitest run src/serve/server.test.ts -t "session artifact routes"
  • cd packages/cli && npx vitest run src/serve/acp-http/transport.test.ts -t "_qwen/session/artifacts|artifact"
  • cd packages/sdk-typescript && npx vitest run test/unit/DaemonClient.test.ts test/unit/DaemonSessionClient.test.ts
  • git diff --check

Note: an initial parallel run of two CLI vitest commands hit the shared coverage temp directory; rerunning server.test.ts alone passed.

Comment thread packages/acp-bridge/src/bridge.ts Outdated
@wenshao

wenshao commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Suggestions — commit e1ef7741

File Issue Suggested fix
packages/cli/src/serve/acp-http/dispatch.ts:453 ACP dispatch pick* functions extract mode, ttlDays, clientRetained, retention, deleteContent using as casts with zero runtime validation. REST routes validate types, but JSON-RPC doesn't. Add typeof checks in pickSessionArtifactPinRequest, pickSessionArtifactUnpinRequest, pickSessionArtifactRemoveRequest mirroring the REST parsers, or extract a shared validator.
packages/acp-bridge/src/bridge.ts:3408 Session close awaits gcArtifactContent synchronously. If content store is large or disk is slow, close blocks — cascading during multi-session shutdown and risking SIGKILL from orchestrators. Make GC fire-and-forget during close. Content will be cleaned up on next session's GC or daemon restart.
packages/acp-bridge/src/sessionArtifactContentStore.ts:21 Global quota (256 MB) shared across all sessions, but gc() only removes content for the calling session. One heavy session can starve all others with no cross-session reclaim path. Scope quota per-session, or add an orphan GC pass that removes entries whose sessionId doesn't match any live session.
packages/acp-bridge/src/bridge.ts:4737 removeSessionArtifact with deleteContent: false only skips the eager GC call. The next GC trigger (session close, rewind, or explicit gcSessionArtifacts) still deletes the orphaned content since no artifact references it. Either add a preservation marker that gc() respects, or rename/document the option to clarify it only defers cleanup.
packages/sdk-typescript/src/daemon/DaemonClient.ts:3212 New methods use encodeURIComponent(...) directly instead of the local urlEncode alias used by 60+ existing call sites in the same file. Replace encodeURIComponent(...) with urlEncode(...) to match the local convention.
Test coverage (5 paths) Untested: (1) pinSessionArtifact with mode: 'content' on non-workspace artifact, (2) removeSessionArtifact with { deleteContent: false } asserting GC is skipped, (3) bridge-level fsckSessionArtifacts/gcSessionArtifacts happy paths, (4) verifyContentRef integration during session restore, (5) gcArtifactContent invocation during session close/rewind. Add bridge.test.ts cases for each path to verify the wiring between bridge, artifact store, and content store.

— qwen3.7-max via Qwen Code /review

@chiga0

chiga0 commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator Author

Processed the latest suggestion summary in a3cf9e966:

  • Fixed the content GC race by serializing refs snapshot + GC behind the artifact store queue.
  • Added JSON-RPC runtime validation for _qwen/session/artifacts/{pin,unpin,remove} options (mode, ttlDays, clientRetained, retention, deleteContent) before dispatch reaches the bridge.
  • Switched the new SDK artifact methods to the existing local urlEncode alias.
  • Added focused regression coverage for the refs lock and ACP invalid-option paths.

I did not change quota semantics or deleteContent: false semantics here: those affect content-retention API/security policy and should be decided explicitly. The separate pin quota policy decision from the earlier thread is still open as well. I also left session-close GC synchronous for now; making close fire-and-forget changes cleanup timing and is better handled with the broader retention-policy decision if reviewers want that direction.

@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 — no blockers. Suggestion-level recommendations are in the Suggestion summary comment below.

@wenshao

wenshao commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

No new Suggestion-level findings this round — all prior suggestions have been addressed or superseded.

@chiga0

chiga0 commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator Author

Updated this stacked content-retention PR by merging the refreshed #6259 base in 80492048e. No conflicts.

Validation after the merge:

  • cd packages/acp-bridge && npm run typecheck
  • cd packages/core && npm run typecheck
  • cd packages/acp-bridge && npx vitest run src/sessionArtifacts.test.ts
  • cd packages/core && npx vitest run src/services/session-artifact-persistence.test.ts
  • cd packages/core && npm run build && cd ../acp-bridge && npm run build && cd ../cli && npm run typecheck && cd ../sdk-typescript && npm run typecheck

Note: an earlier parallel acp-bridge build / cli typecheck run failed because CLI checked bridge declarations while bridge was not built and stale core/dist existed. Rerunning in dependency order passed.

@chiga0

chiga0 commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator Author

Updated this stacked content-retention PR by merging the refreshed #6259 base in 834f17dd3. No conflicts.

Validation after the merge:

  • cd packages/acp-bridge && npx vitest run src/sessionArtifacts.test.ts (98 tests)
  • cd packages/acp-bridge && npm run typecheck
  • npx prettier --check packages/acp-bridge/src/sessionArtifacts.ts packages/acp-bridge/src/sessionArtifacts.test.ts
  • git diff --check
  • cd packages/core && npm run build
  • cd packages/acp-bridge && npm run build
  • cd packages/cli && npm run typecheck
  • cd packages/sdk-typescript && npm run typecheck

Also confirmed the content-retention restore path wires verifyContentRef through artifactContentStore.verifyContentRef(...), so that check remains scoped to this stacked PR rather than #6259.

Comment thread packages/acp-bridge/src/sessionArtifactContentStore.ts
Comment thread packages/acp-bridge/src/sessionArtifactContentStore.ts Outdated
Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment thread packages/acp-bridge/src/bridge.ts Outdated
@chiga0

chiga0 commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator Author

Follow-up for the latest content-retention review: pushed 089040bef.

What changed:

  • verify existing managed content bytes before reusing a content path, failing the pin on hash/size mismatch instead of writing a misleading manifest
  • revalidate workspace containment after opening content and compare the opened handle against both initially resolved and current file stats
  • prune expired retained content for any artifact with contentRef + expired expiresAt, including restored/restorable artifacts
  • reuse an existing verified contentRef for content-mode pin unless explicitly refreshing already pinned content
  • enforce artifact ownership before any workspace-content copy

Validation:

  • cd packages/acp-bridge && npx vitest run src/sessionArtifacts.test.ts src/bridge.test.ts
  • cd packages/acp-bridge && npm run typecheck
  • cd packages/acp-bridge && npm run build
  • cd packages/cli && npm run typecheck
  • cd packages/sdk-typescript && npm run typecheck
  • npx prettier --check packages/acp-bridge/src/sessionArtifactContentStore.ts packages/acp-bridge/src/sessionArtifacts.ts packages/acp-bridge/src/sessionArtifacts.test.ts packages/acp-bridge/src/bridge.ts packages/acp-bridge/src/bridge.test.ts
  • git diff --check

@chiga0

chiga0 commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator Author

Synced this stacked content-retention PR with the refreshed #6259 base in merge commit c54489371. No conflicts.

Validation after the merge:

  • cd packages/core && npx vitest run src/services/session-artifact-persistence.test.ts
  • cd packages/acp-bridge && npx vitest run src/sessionArtifacts.test.ts src/bridge.test.ts
  • cd packages/core && npm run typecheck
  • cd packages/acp-bridge && npm run typecheck
  • npx prettier --check packages/core/src/services/session-artifact-persistence.ts packages/core/src/services/session-artifact-persistence.test.ts packages/acp-bridge/src/sessionArtifacts.ts packages/acp-bridge/src/sessionArtifacts.test.ts
  • git diff --check

Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment thread packages/cli/src/serve/acp-http/dispatch.ts Outdated
Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment thread packages/acp-bridge/src/sessionArtifactContentStore.ts
Comment thread packages/acp-bridge/src/bridge.ts Outdated
await gcArtifactContent(entry);
} catch (error) {
if (removeOptions.deleteContent === true || artifact.contentRef) {
warnings.push('content_delete_preserved');

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.

[Suggestion] The same warning string 'content_delete_preserved' is pushed from three distinct failure contexts: removeSessionArtifact GC failure (line 4747), pinSessionArtifact metadata-mode GC failure (line 4796), and unpinSessionArtifact GC failure (line 4877). The stderr logs differentiate (remove_gc_failed, pin_metadata_gc_failed, unpin_gc_failed), but clients receiving the warning array cannot distinguish which operation's GC failed.

Use distinct warning strings per context, e.g. 'remove_content_preserved', 'pin_metadata_content_preserved', 'unpin_content_preserved', so clients can provide actionable diagnostics.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thanks, I am leaving this one unresolved because changing content_delete_preserved into operation-specific warning strings is a client-visible API contract change. The current stderr actions already distinguish remove vs pin-metadata vs unpin failures; changing the warning vocabulary should get maintainer/design confirmation first.

Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment thread packages/acp-bridge/src/sessionArtifactContentStore.ts
@wenshao

wenshao commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code did not run conflict resolution for this request.

PR #6346 is draft.

@wenshao

wenshao commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code did not run conflict resolution for this request.

PR #6346 is draft.

@wenshao

wenshao commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code did not run conflict resolution for this request.

PR #6346 is draft.

@wenshao

wenshao commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code did not run conflict resolution for this request.

PR #6346 is draft.

@wenshao

wenshao commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

1 similar comment
@wenshao

wenshao commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code did not run conflict resolution for this request.

PR #6346 is draft.

@wenshao

wenshao commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code did not run conflict resolution for this request.

PR #6346 is draft.

@wenshao

wenshao commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code did not run conflict resolution for this request.

PR #6346 is draft.

@wenshao

wenshao commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code did not run conflict resolution for this request.

PR #6346 is draft.

1 similar comment
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code did not run conflict resolution for this request.

PR #6346 is draft.

@wenshao

wenshao commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code did not run conflict resolution for this request.

PR #6346 is draft.

@wenshao

wenshao commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code did not run conflict resolution for this request.

PR #6346 is draft.

@wenshao

wenshao commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code did not run conflict resolution for this request.

PR #6346 is draft.

@wenshao

wenshao commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code did not run conflict resolution for this request.

PR #6346 is draft.

@wenshao
wenshao marked this pull request as ready for review July 11, 2026 12:23
@wenshao
wenshao marked this pull request as draft July 11, 2026 12:24

@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 — no blockers. Suggestions are inline.

// `extNotification` from the (about-to-be-killed) child can't
// seed the early-event buffer for a subsequent load/resume of
// the same persisted id.
ci?.client.markSessionClosed(sessionId);

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.

[Suggestion] killEmptyChannelAfterCleanup introduces a new fast-path that immediately kills the channel when resolvedChannelIdleTimeoutMs() <= 0 && hasNoChannelWork(ci), bypassing reapPendingEmptyChannel + startIdleTimer. This is a behavioral change to channel lifecycle that (1) is not mentioned in the design doc, (2) has no dedicated test coverage, and (3) is inconsistent with closeSessionImpl, which always uses reapPendingEmptyChannel without this fast-path. This optimization is outside the PR's stated scope of content retention — consider calling it out in the PR description and adding test coverage, or splitting it into a separate commit.

— qwen3.7-max via Qwen Code /review

@@ -3436,4 +3494,649 @@ describe('SessionArtifactStore', () => {
],

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.

[Suggestion] The design doc at session-artifacts-persistence-v2-design.md (~line 925) explicitly requires testing "TOCTOU swap" during pin. The openRegularWorkspaceFile function implements post-open checks (realpath re-validation, sameFile inode comparison, nlink check, size re-check), but no test simulates file replacement between fs.open and the post-open stat. The pre-open checks (hardlink, oversized, symlink escape) are tested, but the post-open TOCTOU protection — the most security-sensitive part — has zero coverage. Consider adding a test that swaps the file (via rename or atomic replace) between the open call and the post-open verification.

— qwen3.7-max via Qwen Code /review

@@ -949,9 +955,9 @@ V2 新增的失败路径必须有 structured logs,格式沿用:
- SDK 旧 client 忽略 optional fields 后仍能展示 V1 artifacts。
- V2 -> V1 rollback compatibility:旧 daemon 必须能解析或忽略 unknown `system` subtype,不得导致 session load 崩溃;回滚后 artifact persistence 不恢复是可接受降级。如果当前最低支持版本不能保证这一点,V2 writer 必须 capability-gate 到支持 unknown system record 的版本之后。
- rollback preflight:最低支持旧 daemon 版本加载包含 V2 event/snapshot 的 JSONL;如果未来加入 fork marker,再扩展 rollback fixture。

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.

[Suggestion] The design doc states that GET /artifacts performs a TTL scan that downgrades expired pinned content and triggers best-effort session-scoped GC. However, getSessionArtifacts in bridge.ts only calls entry.artifacts.list(), which refreshes workspace statuses but never calls pruneExpiredPins() or gcArtifactContent(). The new test "does not prune or GC expired pins during artifact GET" explicitly asserts the expired artifact retains retention: 'pinned' and gcSpy was not called — directly contradicting the design doc. Either update the design doc to mark this as a future behavior, or implement the TTL scan during GET as documented.

— qwen3.7-max via Qwen Code /review

'/session/:id/artifacts/:artifactId/pin',
mutate({ strict: true }),
withMutableSession(
'POST /session/:id/artifacts/:artifactId/pin',

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.

[Suggestion] The if (!artifactId) guard is dead code. Express matches :artifactId as a required route segment — a request without it 404s at route selection, and the parameter cannot be empty once the route is matched. The equivalent check in dispatch.ts is correct (JSON-RPC params can genuinely be absent), but this REST-side branch is unreachable. Consider removing it to avoid confusion, or at minimum add a brief comment.

— qwen3.7-max via Qwen Code /review

.json(await bridge.fsckSessionArtifacts(sessionId, { clientId }));
} catch (err) {
sendBridgeError(res, err, {
route: 'GET /session/:id/artifacts/fsck',

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.

[Suggestion] The new fsck, gc, pin, and unpin routes call sendBridgeError on thrown errors, but no test exercises the bridge-error surface for these endpoints. Specifically: SessionNotFoundError from fsck/gc (bridge throws when byId.get(sessionId) misses), and SessionArtifactAuthorizationError from pin/unpin (via denyCrossClientMutation). The sendBridgeError error-mapping path (authorization → 403, not-found → 404) is only as trustworthy as the tests that exercise each route through it. Add at minimum one SessionNotFoundError test for fsck and one authorization error test for pin.

— qwen3.7-max via Qwen Code /review

@wenshao wenshao closed this Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants