fix(cloud-agent-next): report an incomplete worktree restore - #6630
Conversation
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Files Reviewed (2 files)
Previous Review Summaries (5 snapshots, latest commit 620a473)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 620a473)Status: 1 Issue Found | Recommendation: Address before merge Executive SummaryThe new snapshot-path escaping for the agent rules note is correct and test-backed; the only finding is a minor truncation edge case that can split a surrogate pair at the 200-unit cut. Overview
Issue Details (click to expand)SUGGESTION
Files Reviewed (2 files)
Resolved since the previous review ( Fix these issues in Kilo Cloud Previous review (commit 834cd05)Status: No Issues Found | Recommendation: Merge Executive SummaryThe two open findings from the previous review are resolved at HEAD ( Files Reviewed (6 files)
Previous review (commit a392d9c)Status: No Issues Found | Recommendation: Merge Executive SummaryHistory was rewritten (the prior review SHA is no longer an ancestor), so this is a full re-review of the squashed change; the incomplete-restore outcome is wired consistently through the wrapper log line, the agent rules note, the The previously raised truncation note on Files Reviewed (26 files)
Previous review (commit d9928b7)Status: No Issues Found | Recommendation: Merge Executive SummaryThe incremental changes since the previous review only add the Files Reviewed (3 files)
Previous review (commit 3cecfd8)Status: 1 Issue Found | Recommendation: Address before merge Executive SummaryThe incomplete-restore feature is implemented consistently across wrapper, worker, DO, and both clients; the only finding is that the agent-facing rules note truncates its affected-path list without the Overview
Issue Details (click to expand)SUGGESTION
Files Reviewed (26 files)
Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0 Review guidance: REVIEW.md from base branch |
fe37294 to
d9928b7
Compare
6abfa46 to
a392d9c
Compare
Truncating with file.slice cut by UTF-16 code unit, so a path longer than the 200-unit cap with a non-BMP character at the cut point kept a lone high surrogate, which reached the rules note unpaired. Slice the code-point array instead and pin it with a regression test.
…4554) ## Automated docs sync — 2026-09-25 This PR keeps kilo.ai/docs in sync with features merged to [Kilo-Org/cloud](https://github.com/Kilo-Org/cloud) and [Kilo-Org/kilocode](https://github.com/Kilo-Org/kilocode). Every change below links to the merged PR it documents. - Window: `2026-09-24T07:08:33.639Z` → `2026-09-25T07:05:29.302Z` - Verification (docs build + tests): **passing** ### Surface: `cloud-mobile` - Assignees / requested reviewers: @iscekic and @eshurakov - Derivation: Derived from the repository layout. A product surface is a package under packages/ that ships a distinct client, plugin, backend, or hosted service: cli = packages/opencode/ + packages/tui/ + packages/server/ + packages/sdk/ + packages/plugin/; vscode = packages/kilo-vscode/ + packages/kilo-web-ui/ + packages/kilo-ui/; jetbrains = packages/kilo-jetbrains/; gateway = packages/kilo-gateway/; web = packages/kilo-console/ + packages/kilo-indexing/ + packages/kilo-memory/ + packages/kilo-sandbox/. Docs route from the IA tree packages/kilo-docs/pages/ plus docs/jetbrains-vscode-settings-parity.md: each surface lists the pages sections that document it, and the per-platform pages under packages/kilo-docs/pages/code-with-ai/platforms/ map to the matching extension surface (the vscode/ directory to vscode, jetbrains.md to jetbrains). A doc path belongs to the surface with the longest matching prefix; a path that matches none of those prefixes falls to `other` (the explicit other prefixes are listed under other.docs). The cloud surfaces are derived the same way from the Kilo-Org/cloud layout: cloud-mobile = apps/mobile/, cloud-web = apps/web/, cloud-extension = apps/extension/, and cloud-agent = the cloud-agent packages under packages/ (packages/cloud-agent-sdk/ + packages/cloud-agent-profile/). A cloud source names its repository while a bare string still means this repository. The pages under packages/kilo-docs/pages/collaborate/ document the cloud web app (app.kilo.ai: teams dashboard, billing, SSO, adoption dashboard), so they route to cloud-web. No page under packages/kilo-docs/pages/ documents the browser side-panel extension yet, so cloud-extension lists no docs prefix. - Map: `.github/docs-sync/surfaces.json` - Surface map: `cli`, `vscode`, `jetbrains`, `gateway`, `web`, `cloud-mobile`, `cloud-web`, `cloud-extension`, `cloud-agent`, `other` - Source prefixes: `apps/mobile/` (Kilo-Org/cloud) - Doc prefixes: `packages/kilo-docs/pages/code-with-ai/platforms/mobile.md` - Paths that fall to `other`: `packages/kilo-docs/pages/community/`, `packages/kilo-docs/pages/kiloclaw/`, `packages/kilo-docs/pages/contributing/`, `packages/kilo-docs/LEARNINGS.md`, `docs/` - Reviewers are ranked from `Kilo-Org/cloud`; the workflow needs a token with `contents: read` on that repository (repository secret `CROSS_REPO_ACCESS_TOKEN`, exposed to the upsert step as `CLOUD_REPO_TOKEN`). - How the two were computed: Reviewers for `cloud-mobile` are ranked from `Kilo-Org/cloud` git history over `apps/mobile/` (a commit 180 days old counts half as much, half-life 180 days). Bots (author type "Bot" or a login matching /\[bot\]$/i) and people without admin, write, or maintain permission are excluded. ### Changes <!-- docs-sync:changes:start --> | Docs change | Source | | --- | --- | | updated pages/code-with-ai/platforms/mobile.md | [Kilo-Org/cloud#6386](Kilo-Org/cloud#6386) | | updated pages/ai-providers/openai-chatgpt-plus-pro.md | [Kilo-Org/cloud#6702](Kilo-Org/cloud#6702) | | updated pages/code-with-ai/platforms/cloud-agent.md | [Kilo-Org/cloud#6683](Kilo-Org/cloud#6683) | | updated pages/getting-started/byok.md | [Kilo-Org/cloud#6692](Kilo-Org/cloud#6692) | <!-- docs-sync:changes:end --> ### Pending — will retry <!-- docs-sync:pending:start --> _None._ <!-- docs-sync:pending:end --> ### Considered, no docs change needed <!-- docs-sync:skipped:start --> | PR | Reason | | --- | --- | | [Kilo-Org/cloud#6658](Kilo-Org/cloud#6658) | Internal sandbox lifecycle fix with no user-visible workflow or setting. | | [Kilo-Org/cloud#6673](Kilo-Org/cloud#6673) | Internal container CA trust plumbing, no user-facing behavior. | | [Kilo-Org/cloud#6672](Kilo-Org/cloud#6672) | Internal sandbox launch/recovery fix with no documented workflow change. | | [Kilo-Org/cloud#6660](Kilo-Org/cloud#6660) | Internal cloud-agent queue delivery fix; no new command, setting, or workflow for users. | | [Kilo-Org/cloud#6226](Kilo-Org/cloud#6226) | Internal gateway alias-routing change, not user-visible. | | [#14490](#14490) | Tool-call animation and streaming UI polish; users do not need to learn a new workflow. | | [#14530](#14530) | Bug fix restoring intended worktree-pool behavior, no doc change needed. | | [#14529](#14529) | Bug fix restoring tab/panel state across project switches. | | [#14531](#14531) | Reconnect recovery bug fix, restores already-documented behavior. | | [#14532](#14532) | Bug fix keeping session tab title in sync on rename. | | [Kilo-Org/cloud#6088](Kilo-Org/cloud#6088) | Removes internal/admin model-experiment surfaces, not public product docs. | | [Kilo-Org/cloud#6682](Kilo-Org/cloud#6682) | Internal control-socket reconnect race fix, no user-facing change. | | [#14534](#14534) | Transcript re-render performance bug fix. | | [#14535](#14535) | Bug fix preserving the loaded browser page across context switches. | | [Kilo-Org/cloud#6684](Kilo-Org/cloud#6684) | Reverted by Kilo-Org/cloud#6685. | | [Kilo-Org/cloud#6678](Kilo-Org/cloud#6678) | Dead-code constant removal, no user-visible effect. | | [Kilo-Org/cloud#6687](Kilo-Org/cloud#6687) | Removes internal model-experiment maintenance and retains tables, no user-facing change. | | [#14515](#14515) | JetBrains plugin unload crash fix, no documented behavior change. | | [#14520](#14520) | JetBrains transcript/list rendering performance work. | | [Kilo-Org/cloud#6614](Kilo-Org/cloud#6614) | Mobile PR Review header and session title bug fix, no doc change needed. | | [Kilo-Org/cloud#6625](Kilo-Org/cloud#6625) | Internal mobile secure-store error-handling refactor. | | [Kilo-Org/cloud#6624](Kilo-Org/cloud#6624) | Mobile auth bug fix that stops a retry loop; restores expected sign-in behavior with no new setting or workflow. | | [#14310](#14310) | Contributor/CI fix making the kilo-v2 checkout installable; not user-visible product behavior. | | [Kilo-Org/cloud#6611](Kilo-Org/cloud#6611) | Mobile notification-tap fix that selects the session's organization; restores correct behavior rather than adding a learnable feature. | | [Kilo-Org/cloud#6644](Kilo-Org/cloud#6644) | Mobile sign-in layout/alignment polish; no change to what a user must do. | | [Kilo-Org/cloud#6601](Kilo-Org/cloud#6601) | Mobile layout fix keeping empty states clear of the tab bar; purely visual. | | [#14543](#14543) | CI/release infrastructure adding Windows binary code signing; no public docs impact. | | [Kilo-Org/cloud#6616](Kilo-Org/cloud#6616) | Mobile visual defect fixes and a session-title fallback; no new user workflow or setting. | | [Kilo-Org/cloud#6630](Kilo-Org/cloud#6630) | Reports an edge-case partial worktree restore; failure-path plumbing with no new user-facing workflow, target setting, or config. | | [Kilo-Org/cloud#6699](Kilo-Org/cloud#6699) | Cloud Agent e2e stabilization plus internal idle-sandbox capacity handling; not user-visible. | | [#14545](#14545) | Automated JetBrains release/changelog PR; underlying user-facing changes are triaged from their own PRs. | | [Kilo-Org/cloud#6708](Kilo-Org/cloud#6708) | Internal AI-gateway request-logging policy change in the admin panel; no existing public docs surface and no change to how users run Kilo Code. | | [#14533](#14533) | Documentation already shipped with the merged PR. The experimental.task_model_selection flag is gone from the current source, and pages/code-with-ai/agents/model-selection.md, pages/code-with-ai/agents/context-mentions.md, and pages/getting-started/settings/index.md already describe per-task selection as default-on with no stale experiment references. | | [#14510](#14510) | Documentation already shipped with the merged PR. Marketplace companion-skill support is present in the current source (packages/opencode/src/kilocode/marketplace/companions.ts and installer), and pages/customize/marketplace.md already documents installing, publishing, and removing MCP servers with companion skills. | <!-- docs-sync:skipped:end --> --- (bot) Generated by the docs-sync workflow. Humans review and merge; while this PR stays open, the next daily run appends new changes here. Branch: `docs/auto-sync-2026-09-25`. <!-- docs-sync: processed-through 2026-09-25T07:05:29.302Z -->
Changelog for users
Changelog for maintainers
restore_incompletepreparing step emitted as started-then-failed (the materializer drops an unmatched step event).patch_apply_failed,outside_workspace,missing_content,index_reset_failed,unlink_failed,write_failed) and unknown reasons pass through. The restore never retries as a whole; a targeted retry must act on the named reasons and paths.cloud_agent_restore_incompletefrom both the session-ready telemetry and the attach path. The app reuses the existing preparation step stream, so no new UI channel is added; the web drawer line now derives from the shared attempt summary so the row and the panel cannot disagree.Evidence refresh: the repair changed no code, so the full-feature captures below still stand. The earlier log-only proof lines are superseded by these named captures, and the re-run suite logs replace them as regression evidence (cloud-agent-next passed; mobile typecheck and mounted tests passed).
Production report: all three runtime replacements in the session route through the restore paths this change instruments, so each replacement that skipped a diff would have been visible; before this change none was, because the count survived only in a progress line.
E2E proof
[e12] ux-check: Web: trigger a replacement whose restore skips at least one diff; the chat row under the message shows 'Session restore incomplete: N of M files were not restored (). — Android (emulator-5604) mobile equivalent of the web row: the session chat row under the user message renders the group header 'Session restore incomplete: 2 of 5 files were not restored (the patch did not apply). Missing: src/a.ts, src/b.ts' and stays so with the attempt status=completed, 'Preparation complete' absent (e12-scene.log digest; attempt/step rows in e12-fixture.log; alert glyph left to the visual reviewer in e12.png).
[e4] web [needs:fault]: with a session whose restore skipped a diff, the transcript row for the triggering message reads 'Session restore incomplete' with the count and the missing paths instead of… — android emulator-5554: after leaving and re-opening the session the transcript row reads (e4-transcript.txt) text="Session restore incomplete: 2 of 5 files were not restored (the patch did not apply). Missing: src/a.ts, src/b.ts" with the named step text="Session restore incomplete" in the expanded details and no 'Preparation complete' (grep -c = 0); state created by the throwaway fixture e4-inject-fixture.sh (v2 preparing step_snapshot materialized into the session DO stream, mirrors the wrapper event), still e4-transcript.png for the visual reviewer; no UX-DEFECT observed.
[p5] web empty/happy — completed preparation without a restore_incomplete step (android emulator-5554) — android emulator-5554; SCENE p5 OK (p5-scene.log:1); the completed preparation row reads 'Preparation complete' (p5-scene.log:12) with steps 'sandbox boot' (:14), 'workspace setup' (:16), 'cloning' (:18) and 'kilo session' (:20), and the scripted absent checks for 'Session restore incomplete', 'not restored' and 'incomplete' all passed; 'Preparation complete' is the mobile surface's copy for the web 'Environment prepared' row and the expanded group is the mobile row+drawer equivalent; captures p5.png / p5.replay.json.
[p6] backend — a runtime replacement whose restore applies every diff (and a warm reuse) writes no 'bootstrap restore incomplete' line, emits no cloud_agent_restore_incomplete event, and leaves no… — android emulator-5604 (backend surface, proved by logs). Harness leave-and-return passed at 04:15Z (p6-harness.log 'leave-and-return/echo:hi (642044ms) … replacement=20461f465d1b'); the replacement runtime's uploaded wrapper log (p6-wrapper.log) reads 'restore-session: diffs applied=2 skipped=0 total=2' and 'restore-session: completed successfully' with 'session_attach_result … ok:true', 'bootstrap restore incomplete' count 0; the session-home files.tar.gz members (p6-archive-members.txt) contain no restore-incomplete.md; the worker log since the pre-run offset (p6-worker-delta.log, 178 hits…
[e5] web [needs:seed]: a completed preparation whose restore applied every diff still reads 'Environment prepared' and shows no 'incomplete' text anywhere in the transcript (happy path, no regression). — Platform android/emulator-5554, session ses_f355800b9ffcY5rTpoopFA11uU: e5-scene.log shows 'android.widget.Button Preparation complete tappable [40,1445][1042,1546]' over the expanded rows sandbox boot / workspace setup / cloning / kilo session and 'SCENE e5 OK', with zero occurrences of 'incomplete' in the digest; e5-do-steps.log reports 'preparing step_snapshot rows: 4' and 'events mentioning restore_incomplete: 0'. The Android title for the completed state is 'Preparation complete' (the web copy is 'Environment prepared') — same completed-preparation fact. No UX-DEFECT observed.
[e13] ux-check: Web: click that row to open the details panel; the panel's own summary must agree with the row and must not read 'Environment prepared' while the timeline shows the failed 'Session restore… — Platform android/emulator-5554, session ses_f355d695bffed6CUNP3pX6wxrI: e13-scene.log ('SCENE e13 OK') shows the group's own summary line 'android.widget.Button Session restore incomplete: 2 of 5 files were not restored (the patch did not apply). Missing: src/a.ts, src/b.ts' and the expanded step's own detail 'android.widget.TextView Session restore incomplete: 2 of 5 files were not restored (the patch did not apply). Missing: src/a.ts, src/b.ts', identical, with zero 'Preparation complete' lines; the collapse/re-expand taps both kept it. The toggled step key=restore_incomplete status=failed…
Owner request
E2E proof — log excerpts
Review follow-up: snapshot path escaping in the restore rules note (PRRT_kwDORD_mN86lpeBO)
Verdict: valid. The incomplete-restore note at
services/cloud-agent-next/wrapper/src/restore-outcome.ts:144interpolated each affected path raw into a Markdown bullet, and the path is snapshot-supplied (restore-session.ts:1271->extractDiffs,restore-session.ts:1241), so a path carrying a newline or Markdown syntax could add a bullet, a section, or an instruction to a file the agent reads as rules. Fixed in620a4737by rendering each path as data (renderPathAsData,restore-outcome.ts:56): control characters and Unicode line separators become visible\uXXXXescapes, Markdown/HTML syntax is backslash-escaped, and each rendered path is capped at 200 characters.Test command:
Decisive output:
Review follow-up: code-point path truncation in the restore rules note (PRRT_kwDORD_mN86lp9X8)
Verdict: valid.
renderPathAsDataatservices/cloud-agent-next/wrapper/src/restore-outcome.ts:58truncated withfile.slice(0, MAX_RENDERED_PATH_LENGTH), which counts UTF-16 code units. A path longer than the 200-unit cap with a non-BMP character at the cut point kept a lone high surrogate, and the code-point loop below emitted it raw into the rules note.Fixed in
90ef609830: the path is spread into code points ([...file]), that array's length is compared against the cap, and the array is sliced and joined before the…marker is appended, so the whole function is code-point based. TheMAX_RENDERED_PATH_LENGTHdoc comment now says code points. The escaping behaviour, the 200 cap, and everything else are unchanged.Test command:
Decisive output:
The new test fails against the old
file.slice(0, MAX_RENDERED_PATH_LENGTH)(expect(loneSurrogates(rules)).toEqual([])receives a loneU+D83Chigh surrogate) and passes with the fix.bun test src→1632 pass, 0 fail;bun run typecheckandoxfmt --checkare clean.Merge proof — head
a9408fb892origin/main(aa1daf3990) merged into the branch asa9408fb892, no conflicts.Why: the
cloud-agent-nextcheck failed twice at90ef609830ontest/integration/sandbox-control.test.ts:11613— it expectedacceptedand observedqueued, reproducibly in CI while the same commit passed 328/328 locally. The branch was 57 commits behind, andmainreworked that exact behaviour (#6660) with awithoutQueuedDispatchnormaliser documented as "A queued follow-up now dispatches behind an accepted row, so it gains a queued state transiently".Verification at
a9408fb892:The two review fixes survive the merge —
renderPathAsDataand the code-point truncation are still inservices/cloud-agent-next/wrapper/src/restore-outcome.ts, and its tests pass: