Skip to content

fix(mobile): hold live agent rows through a socket reconnect - #6235

Merged
iscekic merged 1 commit into
mainfrom
kwf/owner-live-agents-blank-flash-727c
Sep 17, 2026
Merged

iscekic merged 1 commit into
mainfrom
kwf/owner-live-agents-blank-flash-727c

Conversation

@iscekic

@iscekic iscekic commented Sep 16, 2026 •

Copy link
Copy Markdown
Collaborator

Changelog for users

  • The Agents list keeps showing the last rows while a CLI connection blips and reconnects, instead of flashing empty.
  • The live session count in the header keeps its last value through the same reconnect window instead of dropping to zero.
  • The list still settles on the empty state once the reconnect window expires, so finished sessions are not pinned.

Changelog for maintainers

  • resolveLiveSessionsHold holds the last non-empty live rows for 5000 ms after a socket-emptied set; rows delivered inside the window retire the hold immediately.
  • useLiveSessionsHold arms a release timer because nothing else re-renders at the window's end; without it, a held emptiness would pin stale rows forever.
  • useLiveAgentSessions renders the held rows; the hold is keyed to active.queryKey and gated on active.canRead.
  • A context switch (personal/organization) builds a new query key and never renders the previous context's rows.
  • An empty live set that was never populated is not delayed, so a cold empty account paints the empty state immediately.
  • Review hint: the 5000 ms window is time-boxed rather than tied to the next server answer, because the refresh after a disconnect reads the same transiently empty live set.
  • Row identity is reused across held renders, so held rows do not remount mid-window.
  • Tests cover the pure decision, the timer release, and the real hook chain across a socket cli.disconnected and an empty sessions.list snapshot.

E2E proof

[e1] Agents live list across a brief CLI-socket break — Live CLI row on the Agents tab: e1-source-count.log reads 2a8a9ff2-b8b3-4c85-9b83-e7e3a1fbd269|1|idle; e1-fault-down.log reads 'cloudflare-session-ingest killed (port 13600 refuses; pids 150871 )'; the scene digest captured during the outage reads 'android.widget.TextView 1 LIVE', 'android.widget.TextView Reconnecting…' and 'Greeting, Idle, kwf/kwf-fix-proof-5ec07de-4-b036, CLOUD, and cost 1 cent, 5 minutes ago' with the absent-'Nothing running right now' step passing; six dense samples across a 13 s outage all read live=[1 LIVE] row=[text="Greeting"] empty=[] (e1-brief-samples.log), then…

[e1] Agents live list across a brief CLI-socket break

[e3] Open the Agents tab before any live snapshot has ever arrived: empty state without held rows — e3-login.log reads 'signed in as e2e-empty-kwf-fix-proof-5ec07de-4-b036@example.com (otp)'; e3-source-count.log reads '0' for cli_sessions_v2 of that account; the Agents tab digest reads 'android.widget.TextView 0 LIVE', 'android.widget.TextView Nothing running right now' and 'android.widget.TextView Start a coding task from your phone. Your sessions will appear here.' with no session row (no held rows). Capture e3.png is for the visual reviewer.

/home/igor_kilocode_ai/.local/share/kwf/sections/kwf-fix-proof-5ec07de-4-b036/e2e-cli/cli-tests.log
error: Script not found "turbo"
[e1] Agents live list across a brief CLI-socket break -> pass :: Live CLI row on the Agents tab: e1-source-count.log reads 2a8a9ff2-b8b3-4c85-9b83-e7e3a1fbd269|1|idle; e1-fault-down.log reads 'cloudflare-session-ingest killed (port 13600 refuses; pids 150871 )'; the scene digest captured during the outage reads 'android.widget.TextView 1 LIVE', 'android.widget.TextView Reconnecting…' and 'Greeting, Idle, kwf/kwf-fix-proof-5ec07de-4-b036, CLOUD, and cost 1 cent, 5 minutes ago' with the absent-'Nothing running right now' step passing; six dense samples across a 13 s outage all read live=[1 LIVE] row=[text="Greeting"] empty=[] (e1-brief-samples.log), then post1-post3 read live=[0 LIVE] empty=[text="Nothing running right now"] and the row returned once the CL
[e3] Open the Agents tab before any live snapshot has ever arrived: empty state  -> pass :: e3-login.log reads 'signed in as e2e-empty-kwf-fix-proof-5ec07de-4-b036@example.com (otp)'; e3-source-count.log reads '0' for cli_sessions_v2 of that account; the Agents tab digest reads 'android.widget.TextView 0 LIVE', 'android.widget.TextView Nothing running right now' and 'android.widget.TextView Start a coding task from your phone. Your sessions will appear here.' with no session row (no held rows). Capture e3.png is for the visual reviewer.
/home/igor_kilocode_ai/.local/share/kwf/sections/kwf-fix-proof-5ec07de-4-b036/e2e-mobile-app/e1-scene-final.log
SCENE e1 OK
android.widget.LinearLayout com.kilocode.kiloapp:id/action_bar_root tappable [0,0][1080,2400]
android.widget.FrameLayout android:id/content tappable [0,0][1080,2400]
android.widget.TextView 1 LIVE tappable [58,149][761,186]
android.view.View Agents tappable [58,190][761,296]
android.widget.Button See all tappable [787,203][933,242]
android.widget.TextView SEE ALL tappable [787,203][933,242]
android.widget.Button Filter sessions tappable [970,196][1022,248]
android.widget.EditText Search sessions tappable [163,340][984,449]
android.widget.TextView Reconnecting… tappable [58,482][62,485]
android.widget.Button Greeting, Idle, kwf/kwf-fix-proof-5ec07de-4-b036, CLOUD, and cost 1 cent, 5 minutes ago tappable [0,549][1080,755]
android.widget.TextView CLOUD tappable [93,583][560,620]
android.widget.TextView $0.01 · 5 MINUTES AGO tappable [604,583][957,620]
android.widget.TextView Greeting tappable [93,627][958,673]
android.widget.TextView kwf/kwf-fix-proof-5ec07de-4-b036 tappable [93,683][958,720]
android.widget.Button New session tappable [881,2006][1028,2153]
android.view.View Home, tab, 1 of 3 tappable [0,2195][360,2337]
android.widget.TextView HOME tappable [13,2281][347,2320]
android.view.View Agents, tab, 2 of 3 tappable [360,2195][720,2337]
android.widget.TextView AGENTS tappable [373,2281][707,2320]
android.view.View Profile, tab, 3 of 3 tappable [720,2195][1080,2337]
android.widget.TextView PROFILE tappable [733,2281][1067,2320]
/home/igor_kilocode_ai/.local/share/kwf/sections/kwf-fix-proof-5ec07de-4-b036/e2e-mobile-app/e1-source-count.log
2a8a9ff2-b8b3-4c85-9b83-e7e3a1fbd269|1|idle
/home/igor_kilocode_ai/.local/share/kwf/sections/kwf-fix-proof-5ec07de-4-b036/e2e-mobile-app/e1-fault-down.log
<internal>(port 13600 refuses; pids 150871 )
/home/igor_kilocode_ai/.local/share/kwf/sections/kwf-fix-proof-5ec07de-4-b036/e2e-mobile-app/e1-brief-samples.log
18:09:34 pre live=[1 LIVE] row=[text="Greeting"] empty=[]
DOWN at 18:09:38: <internal>(port 13600 refuses; pids 142382 )
18:09:38 out1 live=[1 LIVE] row=[text="Greeting"] empty=[]
18:09:41 out2 live=[1 LIVE] row=[text="Greeting"] empty=[]
18:09:44 out3 live=[1 LIVE] row=[text="Greeting"] empty=[]
18:09:46 out4 live=[1 LIVE] row=[text="Greeting"] empty=[]
18:09:49 out5 live=[1 LIVE] row=[text="Greeting"] empty=[]
18:09:51 out6 live=[1 LIVE] row=[text="Greeting"] empty=[]
UP at 18:10:00
18:10:00 post1 live=[0 LIVE] row=[] empty=[text="Nothing running right now"]
18:10:03 post2 live=[0 LIVE] row=[] empty=[text="Nothing running right now"]
18:10:06 post3 live=[0 LIVE] row=[] empty=[text="Nothing running right now"]
/home/igor_kilocode_ai/.local/share/kwf/sections/kwf-fix-proof-5ec07de-4-b036/e2e-mobile-app/e3-digest.log
hierarchy: /tmp/kilo-hierarchy.uvNDzt
android.widget.LinearLayout com.kilocode.kiloapp:id/action_bar_root tappable [0,0][1080,2400]
android.widget.FrameLayout android:id/content tappable [0,0][1080,2400]
android.widget.TextView 0 LIVE tappable [58,149][850,186]
android.view.View Agents tappable [58,190][850,296]
android.widget.Button See all tappable [877,203][1023,242]
android.widget.TextView SEE ALL tappable [877,203][1023,242]
android.widget.ScrollView centered-state tappable [0,306][1080,2400]
android.view.ViewGroup centered-state-content tappable [0,958][1080,1442]
android.widget.TextView Nothing running right now tappable [295,1123][786,1188]
android.widget.TextView Start a coding task from your phone. Your sessions will appear here. tappable [56,1197][1025,1289]
android.widget.Button New session tappable [376,1326][704,1442]
android.widget.TextView New session tappable [475,1360][663,1406]
android.view.View Home, tab, 1 of 3 tappable [0,2195][360,2337]
android.widget.TextView HOME tappable [13,2281][347,2320]
android.view.View Agents, tab, 2 of 3 tappable [360,2195][720,2337]
android.widget.TextView AGENTS tappable [373,2281][707,2320]
android.view.View Profile, tab, 3 of 3 tappable [720,2195][1080,2337]
android.widget.TextView PROFILE tappable [733,2281][1067,2320]
/home/igor_kilocode_ai/.local/share/kwf/sections/kwf-fix-proof-5ec07de-4-b036/e2e-mobile-app/e3-login.log
Warning: Activity not started, intent has been delivered to currently running top-most instance.
Mobile E2E preflight passed for android device emulator-5554 in /home/igor_kilocode_ai/.local/share/kwf/wt/kwf-fix-proof-5ec07de-4-b036 (http://127.0.0.1:12881)
login.sh: phase=app-settle t=1789668769
FLOW OK settle-app.js (android emulator-5554)
login.sh: phase=profile-navigation t=1789668792
session.sh: link delivered on emulator-5554
session.sh: open OK device=emulator-5554 route=profile mode=deeplink
login.sh: phase=profile-settle t=1789668794
FLOW OK settle-app.js (android emulator-5554)
login.sh: phase=profile-hierarchy t=1789668802
hierarchy: /tmp/kilo-e2e-signed-in.xwpons (88 elements)
==> the app is signed out; running the full login
login.sh: phase=outbox-snapshot t=1789668809
==> signing out and requesting sign-in code for e2e-empty-kwf-fix-proof-5ec07de-4-b036@example.com
login.sh: phase=request-code t=1789668809
FLOW OK login-request-code.js (android emulator-5554)
login.sh: phase=outbox-delivery t=1789668857
==> verifying sign-in code
login.sh: phase=verify-code t=1789668857
FLOW OK login-verify-code.js (android emulator-5554)
FLOW OK dismiss-dialogs.js (android emulator-5554)
login.sh: phase=account-fixtures t=1789668914
login.sh: phase=report-result t=1789668918
==> signed in as e2e-empty-kwf-fix-proof-5ec07de-4-b036@example.com (otp)
/home/igor_kilocode_ai/.local/share/kwf/sections/kwf-fix-proof-5ec07de-4-b036/e2e-mobile-app/e3-source-count.log
0
Owner request

kwf-fix: proof-5ec07de-4

kwf-fix-pr: #6235

Prove the behaviour of PR #6235 with a live end-to-end run, and make no code change.
The pull request description carries no evidence: the '## E2E proof' section links a file path, which 404s on GitHub.

Run the pull request's own scenarios on a device or a simulator, for every platform its diff touches. Capture the decisive log lines always, and a screenshot as well for a user-visible change. Never a recording: they are gone (owner, 2026-09-16).
Write the evidence under a '## E2E proof' heading in your own pull request description. Never run gh: the driver publishes.
If a scenario fails, name it and say why, and still change no product code: this section proves what the branch already carries.

The pull request description as it stands now

This is the live body; you never need to fetch it, and you must not edit it. The driver merges what your final summary says into it.

## Changelog for users

- The Agents list keeps showing the last rows while a CLI connection blips and reconnects, instead of flashing empty.
- The live session count in the header keeps its last value through the same reconnect window instead of dropping to zero.
- The list still settles on the empty state once the reconnect window expires, so finished sessions are not pinned.

## Changelog for maintainers

- `resolveLiveSessionsHold` holds the last non-empty live rows for 5000 ms after a socket-emptied set; rows delivered inside the window retire the hold immediately.
- `useLiveSessionsHold` arms a release timer because nothing else re-renders at the window's end; without it, a held emptiness would pin stale rows forever.
- `useLiveAgentSessions` renders the held rows; the hold is keyed to `active.queryKey` and gated on `active.canRead`.
- A context switch (personal/organization) builds a new query key and never renders the previous context's rows.
- An empty live set that was never populated is not delayed, so a cold empty account paints the empty state immediately.
- Review hint: the 5000 ms window is time-boxed rather than tied to the next server answer, because the refresh after a disconnect reads the same transiently empty live set.
- Row identity is reused across held renders, so held rows do not remount mid-window.
- Tests cover the pure decision, the timer release, and the real hook chain across a socket `cli.disconnected` and an empty `sessions.list` snapshot.

## E2E proof

**[e1] Agents live list across a brief CLI-socket break** — android emulator-5554: with a live CLI session on the Agents tab (e1-scene.log: `1 LIVE` and `Greeting, Idle, kwf/owner-live-agents-blank-flash-727c, CLOUD, and cost 1 cent, 2 minutes ago`), killing the live-socket service (e1-fault-down3.log: `cloudflare-session-ingest killed (port 12900 refuses; pids 262113 )`) left the list on screen with no empty flash and no zero count — all 22 digests in e1-fault-samples3.log read `live=[text="1 LIVE"] rows=1 empty=0` across the outage and reconnect — while a 12 s outage (e1-fault-samples2.log: `live=[text="0 LIVE"] rows=0 empty=1`) showed the empty…

https://github.com/user-attachments/assets/b7e6829c-f107-438f-a6b3-608b361394d9

**[e3] Open the Agents tab before any live snapshot has ever arrived: the empty state must appear without holding stale rows.** — android emulator-5604: signed out of the account that was showing a live row (longrun-e3-rows.log: '1 LIVE' with 'New session - 2026-09-16T14:44:12.713Z'), signed in to the fresh empty account e2e-empty-owner-live-agents-blank-flash-727c@example.com (longrun-e3-final.log: 'signed in as e2e-empty-owner-live-agents-blank-flash-727c@example.com (otp)'), and the Agents tab rendered the empty state, not held rows: e3-scene.log has 'SCENE e3 OK', '0 LIVE', 'Nothing running right now' and 'Start a coding task from your phone. Your sessions will appear here.', with no session row in the digest; the…

https://github.com/user-attachments/assets/34d583e8-0e3b-424a-89c1-39a0b959dd2b

**[e3] Open the Agents tab before any live snapshot has ever arrived: the empty state must appear without holding stale rows.** — android emulator-5604: signed out of the account that was showing a live row (longrun-e3-rows.log: '1 LIVE' with 'New session - 2026-09-16T14:44:12.713Z'), signed in to the fresh empty account e2e-empty-owner-live-agents-blank-flash-727c@example.com (longrun-e3-final.log: 'signed in as e2e-empty-owner-live-agents-blank-flash-727c@example.com (otp)'), and the Agents tab rendered the empty state, not held rows: e3-scene.log has 'SCENE e3 OK', '0 LIVE', 'Nothing running right now' and 'Start a coding task from your phone. Your sessions will appear here.', with no session row in the digest; the…

![[e3] Open the Agents tab before any live snapshot has ever arrived: the empty state must appear without holding stale rows. — e3-rows.png](https://github.com/user-attachments/assets/29a245f9-066e-4115-97a0-5fdda8f65c6d)

**[e3] Open the Agents tab before any live snapshot has ever arrived: the empty state must appear without holding stale rows.** — android emulator-5604: signed out of the account that was showing a live row (longrun-e3-rows.log: '1 LIVE' with 'New session - 2026-09-16T14:44:12.713Z'), signed in to the fresh empty account e2e-empty-owner-live-agents-blank-flash-727c@example.com (longrun-e3-final.log: 'signed in as e2e-empty-owner-live-agents-blank-flash-727c@example.com (otp)'), and the Agents tab rendered the empty state, not held rows: e3-scene.log has 'SCENE e3 OK', '0 LIVE', 'Nothing running right now' and 'Start a coding task from your phone. Your sessions will appear here.', with no session row in the digest; the…

![[e3] Open the Agents tab before any live snapshot has ever arrived: the empty state must appear without holding stale rows. — e3.png](https://github.com/user-attachments/assets/708b855a-e7c0-4ca9-9b91-8c473826d122)

[e3] Open the Agents tab before any live snapshot has ever arrived: empty state -> pass :: android emulator-5604: signed out of the account that was showing a live row (longrun-e3-rows.log: '1 LIVE' with 'New session - 2026-09-16T14:44:12.713Z'), signed in to the fresh empty account e2e-empty-owner-live-agents-blank-flash-727c@example.com (longrun-e3-final.log: 'signed in as e2e-empty-owner-live-agents-blank-flash-727c@example.com (otp)'), and the Agents tab rendered the empty state, not held rows: e3-scene.log has 'SCENE e3 OK', '0 LIVE', 'Nothing running right now' and 'Start a coding task from your phone. Your sessions will appear here.', with no session row in the digest; the hold's source is empty in the same run (e3-source-count.log prints '0' for cli_sessions of that accoun
[e1] Agents live list across a brief CLI-socket break -> pass :: android emulator-5554: with a live CLI session on the Agents tab (e1-scene.log: 1 LIVE and `Greeting, Idle, kwf/owner-live-agents-blank-flash-727c, CLOUD, and cost 1 cent, 2

## Follow-ups (not changed here)

Open findings (not fixed here)

  • the '## E2E proof' section carries no media and no log excerpt
e1-live.trim.mp4.trim.mp4

e1-state4-recovered

e2

e3

e1

e3

e1

e1-outage-1

e1-fault-1

e1

e3

e1

e1-state-live-0-LIVE-rows-0-empty-1

e3

@iscekic
iscekic marked this pull request as draft September 16, 2026 15:17
Comment thread apps/mobile/src/lib/live-sessions-render-hold.ts
@kilo-code-bot

kilo-code-bot Bot commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

Re-verified the reworked reconnect hold (monotonic performance.now(), the rebase for a reading behind the recorded start, and the timer-driven release) with its callers and tests; no defect was found on the changed lines.

Files Reviewed (8 files)
  • apps/mobile/src/lib/live-sessions-render-hold.ts
  • apps/mobile/src/lib/live-sessions-render-hold.test.ts
  • apps/mobile/src/lib/hooks/use-live-sessions-hold.ts
  • apps/mobile/src/lib/hooks/use-live-sessions-hold.mounted.test.tsx
  • apps/mobile/src/lib/hooks/use-agent-sessions.ts
  • apps/mobile/src/lib/hooks/use-agent-sessions.live-blank-flash.mounted.test.tsx
  • apps/mobile/src/lib/hooks/use-agent-sessions.combined.mounted.test.tsx
  • apps/mobile/src/components/agents/agents-tab-badge.mounted.test.tsx
Previous Review Summary (commit 0521751)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 0521751)

Status: 1 Issues Found | Recommendation: Address before merge

Executive Summary

One low-severity robustness issue in the new reconnect hold: its 5000 ms window is measured with a non-monotonic wall clock, so a backward clock adjustment can pin held live rows past the window.

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
apps/mobile/src/lib/live-sessions-render-hold.ts 75 elapsedMs can be negative on a backward wall-clock step, extending the hold beyond its window
Files Reviewed (8 files)
  • apps/mobile/src/lib/live-sessions-render-hold.ts - 1 issue
  • apps/mobile/src/lib/live-sessions-render-hold.test.ts - 0 issues
  • apps/mobile/src/lib/hooks/use-live-sessions-hold.ts - 0 issues
  • apps/mobile/src/lib/hooks/use-live-sessions-hold.mounted.test.tsx - 0 issues
  • apps/mobile/src/lib/hooks/use-agent-sessions.ts - 0 issues
  • apps/mobile/src/lib/hooks/use-agent-sessions.live-blank-flash.mounted.test.tsx - 0 issues
  • apps/mobile/src/lib/hooks/use-agent-sessions.combined.mounted.test.tsx - 0 issues
  • apps/mobile/src/components/agents/agents-tab-badge.mounted.test.tsx - 0 issues

Fix these issues in Kilo Cloud


Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0

Review guidance: REVIEW.md from base branch main

@iscekic
iscekic force-pushed the kwf/owner-live-agents-blank-flash-727c branch from c5ba82e to 5ec07de Compare September 16, 2026 16:03
@iscekic
iscekic marked this pull request as ready for review September 16, 2026 16:23
@iscekic

iscekic commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator Author

This description names a scenario the proof did not capture:

  • not proved live: On the Agents tab with a live CLI session showing, break the CLI socket briefly (kill/restart the CLI or toggle device network off for a few seconds) so the live set empties and reconnects: the list must stay on screen with no flash of the empty state and the header live count must not flash 0. (no capture cited it)

A repeated proof run rebuilds the same evidence, so no proof run is dispatched for a named gap. Merging with this gap open is your decision.

@iscekic iscekic added the human-ready The PR is ready for human review. label Sep 17, 2026
@iscekic iscekic self-assigned this Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

human-ready The PR is ready for human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants