Skip to content

fix(mobile): prevent wrapped composer text clipping - #4950

Open
iscekic wants to merge 39 commits into
mainfrom
composer-wrap-fix-2ec5
Open

fix(mobile): prevent wrapped composer text clipping#4950
iscekic wants to merge 39 commits into
mainfrom
composer-wrap-fix-2ec5

Conversation

@iscekic

@iscekic iscekic commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Summary

What

  • Correct both mobile composer mirror-text widths by subtracting the input border as well as horizontal padding.
  • Share the session composer's rendered horizontal-padding constant with its measurement helper.
  • Floor fractional layout widths so measurement never rounds wider than the rendered input.

Why

The hidden measurement text was 2px wider than each real text area. At a wrap boundary it could undercount a line, leaving text clipped while scrolling stayed disabled.

How

  • Add focused w - 34 (session composer) and w - 26 (kilo-chat composer) helpers with arithmetic regression tests.
  • Preserve existing auto-grow, five-line scroll threshold, and below-cap swipe-to-dismiss behavior.
  • Unit tests pin pixel arithmetic rather than rendered wrapping: the mobile Vitest projects are layout-free (node / react-test-renderer) and do not run Yoga or fire onLayout; iOS E2E covers real rendering.
  • Bug-reproduction gate skipped: the root cause was fully traced in code at both exact call sites.

Verification

  • pnpm format
  • pnpm typecheck
  • pnpm lint
  • pnpm check:unused
  • pnpm test — 296 files, 2,582 tests
  • git diff --check
  • iOS E2E: incremental wrap sweep, cap/scroll, swipe-to-dismiss, and send/reset

Visual Changes

After-only screenshots: the before state was not pursued because the 2px boundary is input/font dependent and the root cause was already fully traced in code.

Incremental wrap sweep (4 lines) Five-line cap and scroll
c1-4line-state.png c2-capped-state.png

Reviewer Notes

  • new-session-prompt.tsx is intentionally unchanged: its layout geometry is already correct.
  • Both composers use the same hook and had the same 2px issue, so both call sites are fixed.

Reviewer note: This PR temporarily contained changes from #4960 to unblock e2e. Those changes are removed. #4960 lands the e2e harness.

@iscekic
iscekic requested a review from jeanduplessis August 1, 2026 17:23
@iscekic iscekic self-assigned this Aug 1, 2026
@iscekic
iscekic requested a review from pandemicsyn August 1, 2026 17:23
@kilo-code-bot

kilo-code-bot Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The latest commit moves the lock-contention test out of dev/local/mobile-android.test.ts into dev/local/process-lock.test.ts with an accurate name and no unused binding, which resolves the only outstanding finding; high confidence that the incremental range is clean.

Files Reviewed (2 files in incremental range)
  • dev/local/process-lock.test.ts — new contention test, 0 issues
  • dev/local/mobile-android.test.ts — stale harness test and its now-unneeded process-lock import removed, 0 issues
Verification notes
  • Incremental range reviewed: b23fabe..1b50a2a (test(dev): move lock contention test to process-lock), 2 files / 19 insertions / 20 deletions.
  • Previous WARNING on dev/local/mobile-android.test.ts:375 is resolved: the test that claimed to exercise a removed emulator-start retry guard is deleted, the unused holder binding is gone, and the withProcessLockAsync import was dropped with it.
  • Verified the relocated test is meaningful against current code: acquireProcessLock uses retries: 0 when waitMs is 0 (dev/local/process-lock.ts:40-49) and throws "<label> is locked by another live process" (dev/local/process-lock.ts:52), so the nested acquisition inside the held lock deterministically matches the assertion. The outer lock is released in finally, so no lock-refresh interval is leaked.
  • Confirmed fs, os, and path are still used extensively in dev/local/mobile-android.test.ts (81 usages), so the deletion left no unused imports.
  • Both tests create isolated roots with mkdtempSync and clean up in finally; no shared state between them.
  • All 35 pre-existing inline comments return line: null from the GitHub API and point at files no longer present in the PR diff, so every prior thread is outdated; none were carried forward and no duplicates were created.
  • No Markdown, landing-page, or SEO-relevant files are in the diff; no new subscriptions, timers, listeners, or retained references were introduced, so there is no memory-leak risk in this range.
  • Read-only: no files edited, no commits, nothing executed. HEAD 1b50a2a re-confirmed immediately before publishing.

Assumption stated for transparency: the six apps/mobile composer-width files were unchanged in this incremental range and were covered in earlier review rounds, so they were not re-analyzed.

Previous Review Summaries (8 snapshots, latest commit b23fabe)

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

Previous review (commit b23fabe)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The latest commit reverted the shared e2e harness back to base, but dev/local/mobile-android.test.ts kept a test that asserts an emulator retry guard the revert removed.

Overview

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

WARNING

File Line Issue
dev/local/mobile-android.test.ts 375 Leftover harness test: its name asserts an emulator-start retry guard that no longer exists at HEAD, it only exercises process-lock, and holder on line 379 is never read
Files Reviewed (4 files in incremental range)
  • dev/local/mobile-android.ts — reverted to base, 0 issues
  • dev/local/mobile-simulator.ts — reverted to base, 0 issues
  • dev/local/tmux.ts — reverted to base, 0 issues
  • dev/seed/app/github-integration-copy.ts — deleted (back to base), 0 issues
  • dev/local/mobile-android.test.ts — 1 issue (stale relative to the revert above)
Verification notes
  • Incremental range reviewed: 263407e..b23fabe (chore(dev): revert shared e2e harness files), which restores mobile-android.ts, mobile-simulator.ts, and tmux.ts to base and deletes the seed topic copy.
  • The PR diff versus base b9ebddc is now only 7 files / 94 insertions: the six apps/mobile composer-width files plus dev/local/mobile-android.test.ts.
  • Confirmed the reverted code paths are gone at HEAD: locked by another live process now appears only in dev/local/process-lock.ts:52 and in the stale test's assertion; emulator-start performs a single startAndroidEmulator call with no relaunch or GPU fallback.
  • The stale test still passes (nested withProcessLockAsync with waitMs = 0 throws immediately), so it is misleading rather than failing; dev/ is outside the pnpm lint and pnpm typecheck scopes, so the unused holder binding does not break CI.
  • All 34 pre-existing inline comments return line: null from the GitHub API, i.e. every prior thread is outdated against the current diff; none were carried forward and no duplicate was created.
  • No Markdown, landing-page, or SEO-relevant files remain in the diff; no new subscriptions, timers, listeners, or retained references were introduced, so there is no memory-leak risk in this range.
  • Read-only: no files edited, no commits, nothing executed. Verification claims in the PR body were not re-run.

Assumptions stated for transparency: the six apps/mobile composer files were unchanged in this incremental range and were covered in earlier review rounds, so they were not re-analyzed; one gh api read of the summary comment by ID was blocked, so the trusted comment ID from the review context and a paginated issue-comments read were used instead.

Fix these issues in Kilo Cloud

Previous review (commit 263407e)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental range between the previously reviewed commit c370dd8 and current HEAD 263407e contains no content changes — the branch was re-committed with an identical tree — so there is nothing new to review and no findings remain.

Files Reviewed (0 files)

No files changed in the incremental range c370dd8..263407e.

Verification notes
  • git diff c370dd8..263407e is empty: both commits share the same parent (c5611354) and identical trees, so the branch was amended/re-committed without content changes.
  • Merge base with main is unchanged (b9ebddc), so the PR diff versus base is also unchanged from the previous review.
  • Current PR changed files (apps/mobile/src/components/** composer width fixes, dev/local/mobile-android*, dev/local/mobile-simulator.ts, dev/local/tmux.ts, dev/seed/app/github-integration-copy.ts) were covered in prior review rounds and are outside the incremental scope.
  • Every inline review comment on this PR returns line: null from the GitHub API, i.e. all existing threads are outdated against the current diff; none were carried forward.
  • No Markdown, landing-page, or SEO-relevant files changed in this range; no new subscriptions, timers, listeners, or retained references were introduced, so there is no memory-leak risk to assess.
  • Read-only: no files edited, no commits, nothing executed.

Assumptions stated for transparency: findings on files outside the incremental diff were not re-analyzed, per incremental review scope; the summary comment target could not be pre-read because that API call was blocked, so the trusted comment ID from the review context was used as-is.

Previous review (commit c370dd8)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental range removes the three dev/** assertions that contradicted the reverted apps/mobile/e2e harness and repoints the seed help text at the surviving stub, resolving every finding from the previous review with high confidence.

Files Reviewed (3 files)

Incremental scope since 278a536: commit fix(dev): drop test assertions for the reverted e2e harness.

  • dev/local/mobile-workflow.test.ts
  • dev/local/appium-wrapper.test.ts
  • dev/seed/app/github-integration-copy.ts
Verification notes

Previously reported findings, re-verified against HEAD c370dd8:

Previous finding Status
mobile-workflow.test.ts asserted launchBudget/120000 and a sliced deadline Resolved — now asserts launchTimeout = ctx.platform === 'android' ? 420000 : 30000, matching flows/open-app.js:30; the deadline assertion is gone
mobile-workflow.test.ts asserted 300000 : 120000 Resolved — now asserts 300000 : 15000, matching flows/settle-app.js:16
appium-wrapper.test.ts asserted survived SIGKILL and a non-zero exit Resolved — the stop_server failure test was deleted
appium-wrapper.test.ts stale-pid test was vacuous Resolved — the test was deleted
github-integration-copy.ts help text pointed at the deleted github-stub.sh Resolved — now names apps/mobile/e2e/github-api-stub, which exists (server.mjs)

Additional checks on the incremental range:

  • After the two deletions, dev/local/appium-wrapper.test.ts still uses every import and helper (spawnSync, os, path, fs, repoRoot, executable, slugFor, portsFor), so no unused-symbol lint regression.
  • Repository-wide search finds no remaining references to survived SIGKILL, launchBudget, or github-stub.sh, so the revert left no dangling references.
  • mobile-workflow.test.ts:115 still expects [3000, 5000, 5000, 5000, 5000, 15000]; the timeout: (\d+) regex correctly skips the ternary on settle-app.js:16, so that unchanged assertion remains consistent.
  • dev/local/mobile-workflow.test.ts and dev/local/appium-wrapper.test.ts are now identical to main, so they no longer appear in the PR diff and carry no inline-commentable findings.
  • Every existing inline review thread on this PR reports line: null from the GitHub API, i.e. all are outdated against the current diff; none were carried forward.
  • Read-only: no files edited, no commits, nothing executed.

Not applicable to this range: no Markdown, landing-page, or SEO-relevant files changed; no new subscriptions, timers, listeners, or retained references were introduced, so there is no memory-leak risk.

Assumption stated for transparency: findings on files outside the incremental diff (including the apps/mobile/src/components/** composer changes reviewed in earlier rounds) were not re-analyzed, per incremental review scope.

Previous review (commit 278a536)

Status: 5 Issues Found | Recommendation: Address before merge

Executive Summary

The latest commit reverted apps/mobile/e2e/ to base but left the matching assertions in dev/local/, so four assertions across mobile-workflow.test.ts and appium-wrapper.test.ts now reference flow and wrapper code that no longer exists and cannot pass.

Overview

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

CRITICAL

File Line Issue
dev/local/mobile-workflow.test.ts 20 Asserts launchBudget = ... 420000 : 120000 and (line 21) a deadline sliced wait; reverted open-app.js:30 has launchTimeout = ... 420000 : 30000 and no deadline
dev/local/mobile-workflow.test.ts 122 Asserts 300000 : 120000; reverted settle-app.js:16 has 300000 : 15000
dev/local/appium-wrapper.test.ts 457 Asserts /survived SIGKILL/ and a non-zero exit; reverted stop_server has no escalation and always returns 0

WARNING

File Line Issue
dev/local/appium-wrapper.test.ts 558 Test is now vacuous: stop_server can no longer fail, and its lsof stub is unreachable — silent coverage loss

SUGGESTION

File Line Issue
dev/seed/app/github-integration-copy.ts 82 Help text points at github-stub.sh, deleted by the revert; the real stub is apps/mobile/e2e/github-api-stub/server.mjs
Files Reviewed (5 files)

Incremental scope since c561135: the only change is chore(e2e): revert apps/mobile/e2e to base, which removes every apps/mobile/e2e/** change from the PR. Review therefore covers that revert plus the still-changed dev/** files whose assertions depend on it.

  • apps/mobile/e2e/ (reverted to base: AGENTS.md, appium.sh, flows/open-app.js, flows/settle-app.js, github-stub.sh deleted, login.sh, logout.sh, record.sh, remote-cli.sh) - source of all 5 issues
  • dev/local/mobile-workflow.test.ts - 2 issues
  • dev/local/appium-wrapper.test.ts - 2 issues
  • dev/seed/app/github-integration-copy.ts - 1 issue
  • dev/local/tmux.ts, dev/local/mobile-android.test.ts - no new issues
Verification notes
  • Confirmed against HEAD 278a536 that apps/mobile/e2e/appium.sh contains no survived SIGKILL, no LSOF_OK, and a stop_server that ends in rm -f (always 0); flows/open-app.js:30 has launchTimeout/30000 with no deadline; flows/settle-app.js:16 has 15000.
  • Traced the two appium.sh tests statement by statement under their own stubs (kill exits 0, ps prints appium, nc exits 1, counted curl): the stop test exits 0 where it asserts non-zero, and the ensure test still exits 0 but only because stop_server cannot fail.
  • No files were edited, no commits made, and nothing was executed.
  • Prior findings on apps/mobile/e2e/appium.sh and apps/mobile/e2e/github-stub.sh are omitted: those files are no longer changed by this PR, so their threads are outdated. Findings on unchanged dev/** lines are likewise not carried forward.
  • No Markdown or landing-page files remain changed, so the Markdown-image and SEO rules do not apply. No new subscriptions, timers, or retained references were introduced, so there is no memory-leak risk in this range.

Fix these issues in Kilo Cloud

Previous review (commit c561135)

Status: No Issues Found | Recommendation: Merge

Executive Summary

Incremental scope since 6204b9f is limited to the stat ordering fix in github-stub.sh and the sentinel-pid guard in appium-wrapper.test.ts; both previously reported findings are resolved at HEAD and no new issues were found (high confidence).

Files Reviewed (2 files)

Incremental scope since 6204b9f:

  • apps/mobile/e2e/github-stub.sh
  • dev/local/appium-wrapper.test.ts
Previously reported issues resolved in this range
  • apps/mobile/e2e/github-stub.sh:105 — the probe order is now GNU-first (stat -c '%a', then BSD stat -f '%p'). On Linux the GNU form succeeds directly; on macOS -c is not a valid option, so the BSD -f '%p' fallback runs and ${perms: -3} extracts the permission triplet from the 100644-style mode. The new [[ "$perms" =~ ^[0-7]+$ ]] gate also means a non-numeric probe result can no longer reach chmod, so mv installs the original mode instead of the umask mode of the > "$tmp" file.
  • dev/local/appium-wrapper.test.ts:574 — cleanup now compares the pid-file value against SENTINEL_PID and only signals when they differ, so an early failure that leaves appium.pid at the seeded 99999 no longer SIGKILLs a pid the test does not own.
Verification notes
  • Re-traced ensure_server with this test's stubs: the stale-pid branch reaches stop_server, which returns 1 and keeps the pid file at the sentinel, and only the port loop's echo $! >"$STATE_DIR/appium.pid" (appium.sh:136) replaces it. That write is the same statement that follows the single nohup spawn, so on any path where a stub server exists the pid file holds the test-owned pid and the guard still reaps it; on paths where no server was spawned the sentinel is correctly left alone.
  • perms is a local declared at github-stub.sh:90 and unconditionally assigned, so the new [[ ... ]] test is safe under set -u; ${perms: -3} on a shorter value (for example mode 0) returns the whole string rather than erroring.
  • The comment block at github-stub.sh:101-103 describes GNU's stat -f behaviour as an exit-0 ? placeholder; on GNU coreutils -f is --file-system and treats '%p' as an operand, so the exact failure mode differs, but the new GNU-first ordering is correct under either behaviour. Comment wording only, no code impact, so not filed as a finding.
  • claim_port at github-stub.sh:80 still uses the BSD-first shape for the mtime probe, but that line is unchanged in this PR and is therefore out of incremental scope.
  • No secrets, tokens, or headers are logged by the changed lines. The test change only adds a constant and a comparison, so no new subscriptions, timers, or retained references were introduced and there is no memory-leak risk in this range.
  • No files were edited, no commits made, and nothing was executed; both files were read only.
  • No Markdown or landing-page files changed, so the Markdown-image and SEO rules do not apply.
  • Findings from files outside the incremental diff (for example dev/local/mobile-android.ts, dev/local/tmux.ts, apps/mobile/e2e/appium.sh) are not carried forward into this summary; their existing inline threads remain the record.

Previous review (commit 6204b9f)

Status: 2 Issues Found | Recommendation: Address before merge

Executive Summary

The stat portability fix in github-stub.sh stops the set -e abort but leaves the GNU branch unreachable, so a developer's .env.local can still be rewritten with umask permissions instead of its original mode.

Overview

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

SUGGESTION

File Line Issue
apps/mobile/e2e/github-stub.sh 101 BSD-first ordering keeps the GNU branch dead: GNU stat -f '%p' prints ? and exits 0, so stat -c '%a' never runs, chmod "?" is swallowed, and mv installs the umask mode of the > "$tmp" file (widening a 0600 env file to 0644)
dev/local/appium-wrapper.test.ts 568 If the run fails before appium.sh:136 overwrites appium.pid, the cleanup SIGKILLs the seeded sentinel pid 99999, which is a reachable pid on Linux hosts with pid_max = 4194304
Files Reviewed (2 files)

Incremental scope since 154bb92:

  • apps/mobile/e2e/github-stub.sh - 1 issue
  • dev/local/appium-wrapper.test.ts - 1 issue
Previously reported issues resolved in this range
  • dev/local/appium-wrapper.test.ts:573 — the finally block now reads the pid file and sends a real process.kill(..., 'SIGKILL') before rmSync, so the stub Appium server started by ensure_server no longer leaks on the happy path (residual sentinel-pid concern noted above).
  • dev/local/appium-wrapper.test.ts:452KILO_APPIUM_HOME: root now isolates APPIUM_HOME inside the test's temp root; appium.sh:21 honours it, so nothing is written to $HOME/.cache/kilo-appium.
  • apps/mobile/e2e/github-stub.sh:101 — the || true terminator removes the set -e abort in remove_env_line; even when perms degrades to a non-numeric value, chmod failure is swallowed and mv still runs, so the stub's env line, state dir, and temp file are cleaned up on Linux.
Verification notes
  • Traced ensure_server with this test's stubs: nc reports every port free, curl succeeds from the second call, lsof stays blind for three iterations, so the run takes the blind-adopt branch after exactly one nohup stub spawn — the pid recorded at appium.sh:136 is the one long-lived process, so the new cleanup does reap the leak it targets.
  • stop_server keeps appium.pid at 99999 when it returns 1, and only the port loop overwrites it; that is the window where the cleanup would signal a pid the test does not own.
  • The GNU stat claim could not be executed in this read-only session (command execution was declined), so it rests on GNU coreutils' documented behaviour of emitting ? for unrecognized directives with exit status 0. Reordering the two stat forms makes the code correct under either behaviour, which is why it is filed as a suggestion rather than a warning.
  • claim_port at github-stub.sh:80 shares the same BSD-first stat shape and would degrade to its date +%s fallback under the same reasoning, but that line is unchanged in this PR and was not filed as a finding.
  • No secrets, tokens, or auth headers are logged by the changed lines; no product-code subscriptions, timers, or retained closures were added, so no memory-leak risk in this range.
  • No files were edited, no commits made, and nothing was executed; the shell script and test were read only.
  • No Markdown or landing-page files changed, so the Markdown-image and SEO rules do not apply.

Fix these issues in Kilo Cloud

Previous review (commit 154bb92)

Status: 3 Issues Found | Recommendation: Address before merge

Executive Summary

The new ensure_server regression test starts a stubbed Appium server through nohup and never stops it, leaking a permanently running node process on every test run; the previously reported BSD-only stat -f '%p' in github-stub.sh is still unfixed.

Overview

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

WARNING

File Line Issue
dev/local/appium-wrapper.test.ts 562 The test starts the stub Appium server (nohup ... &) but never calls server stop; the stub ends in setInterval(() => {}, 1000) so it runs forever, and rmSync(root) destroys appium.pid making the leaked pid unrecoverable
apps/mobile/e2e/github-stub.sh 101 stat -f '%p' is BSD-only; on GNU coreutils -f is --file-system so the command exits 1 and the perms=$(...) assignment aborts remove_env_line under set -e, leaving the stub's .env.local line, state dir, and temp file behind (unchanged in this range, re-verified at HEAD)

SUGGESTION

File Line Issue
dev/local/appium-wrapper.test.ts 451 The stop_server test omits KILO_APPIUM_HOME, so appium.sh creates $HOME/.cache/kilo-appium outside the test's temp root; the sibling test added in the same commit already isolates it
Files Reviewed (2 files)

Incremental scope since fa5e8a0:

  • dev/local/appium-wrapper.test.ts - 2 issues
  • apps/mobile/e2e/appium.sh
Previously reported issues resolved in this range
  • apps/mobile/e2e/appium.sh:198 — the three cleanup call sites (lines 116, 119, 170) now use stop_server || true, so a pid that survives SIGKILL no longer aborts ensure_server under set -e. Explicit server stop (line 215) still propagates the failure, which is the intended behavior.
Verification notes
  • Traced the new test's stale-pid path through ensure_server step by step with the provided stubs (kill always succeeds, ps prints appium, nc reports the port free, lsof prints nothing, curl fails once then succeeds). The path is reached as intended and ends in the blind-adopt branch, so the test does assert what its comment claims.
  • Confirmed the leaked stub cannot be reaped by the script: enable -n kill plus the no-op kill stub make every kill/kill -9 a lie, and the pid file is deleted with the temp root.
  • Both new tests spend ~13-15 s in stop_server's escalation sleeps against a 30 s spawnSync timeout, with ~30 stub node spawns on top; the margin is thin on a loaded machine, so watch for flakiness even though the arithmetic passes locally.
  • assert.ok(actualPort >= 4730) is sound: ensure_server sets base_port from the pre-seeded server.port (4730), not from ports.js.
  • stop_server keeping state on a SIGKILL survivor is now partly moot inside ensure_server, since the retry writes a new pid over appium.pid; the survivor is still reported on stderr and the port loop bumps past its listener, so no finding was raised.
  • No secrets, tokens, or auth headers are logged; no new app-code subscriptions, timers, or retained closures, so no product-side memory-leak risk in this range.
  • Nothing was executed: the shell scripts and tests were read only, and no lint/typecheck/test command was run (read-only review mode).
  • No Markdown or landing-page files changed, so the Markdown-image and SEO rules do not apply.

Fix these issues in Kilo Cloud

Previous review (commit fa5e8a0)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The permission-preserving stat -f '%p' added to github-stub.sh is BSD-only and exits non-zero on Linux, so under set -e it aborts remove_env_line and leaves the stub's .env.local line, state directory, and a stray temp file behin

[Snapshot truncated.]

Additional previous summary content was truncated to keep this comment within platform limits.


Reviewed by claude-opus-5 · Input: 28 · Output: 5.3K · Cached: 529.6K

Review guidance: REVIEW.md from base branch main

@iscekic iscekic added the human-ready The PR is ready for human review. label Aug 1, 2026
iscekic added 24 commits August 2, 2026 00:16
…olerant flows

- rewrite e2e/AGENTS.md in simplified technical English: task-structured,
  scripts own the mechanics, 410 -> ~215 lines
- new e2e/github-stub.sh: PR-review stub server + env line + token seed
  as one reversible command pair; failed starts clean up after themselves
- emulator-start --wait retries a failed launch once itself (boot-envelope
  timeout keeps the GPU, other failures switch to software rendering);
  the runbook's manual GPU decision table is gone
- appium.sh hierarchy writes to a file and prints the path; a raw XML dump
  into an agent session is large enough to kill it silently
- raise iOS cold-launch/settle budgets to 120s (timed out under parallel
  dual-bundle load); document ANR-under-load handling
- simulator claims record originalDeviceName and bootedByClaim intent in
  the initial write, so a lost post-boot rewrite can no longer leak a
  renamed or still-booted device on release
- emulator-start retry: rethrow the already-exists guard (a retry would
  tear down a live emulator) and keep attempt 1's error when the interim
  teardown itself fails
- github-stub: curl transport failures now route through seed_fail, so a
  failed start still rolls back the session and env line
- github-stub: guard against a .env.local without a trailing newline
  before appending, so the marker line can never merge into (and later
  delete) an existing variable
- tmux targets use exact-match (=name): bare -t prefix-matches, so a sibling
  worktree whose slug extends ours could be probed or killed in its place
- simulator claims write atomically (temp + rename/link): in-place truncation
  let an unlocked reader see a partial file, judge it corrupt, and delete a
  live claim
- a claim abandoned after its boot (rename failure) now powers the device
  off, or keeps the claim when even that fails
- simctl bootstatus is bounded (15 min) so a wedged CoreSimulator cannot
  block a claim forever
- emulator-start tears down after a failed second attempt too
- github-stub start claims its port machine-globally and verifies its own
  session survived, so two worktrees cannot share one stub silently; stop
  releases the claim
- appium: driver install re-checks inside the install lock; server startup
  verifies port ownership before adopting a listener; stop escalates to
  SIGKILL instead of orphaning a live server
- record.sh serializes start/stop per device with the shared process lock
- open-app consumes its launch budget in 30s slices so an ANR dialog is
  answered promptly instead of after the full wait
- login.sh matches the recipient literally, not as a regex
- hierarchy mktemp template puts Xs last (BSD mktemp leaves embedded Xs
  literal, making every bare call after the first fail)
- AGENTS.md: per-platform mixed fixtures, pull-and-grep for ADB dumps,
  stub-stop token residue note
- mobile-workflow tests updated for the raised launch/settle budgets
- appium: the recorded-server fast path now verifies the recorded pid owns
  the listener before adopting (a recycled pid plus a sibling's server on
  the same port answered /status and got adopted)
- github-stub: seeding extracted into seed_token; new 'seed <email>'
  subcommand adds a token row for a second signed-in account (the other
  platform's verifier) without touching the running stub
- github-stub: the port claim covers only the choose-to-bind window and an
  EXIT trap releases it, so an interrupted start cannot leak claims and
  erode the port range
- github-stub: githubUserId carries the pid — concurrent seeds in the same
  second cannot collide on the unique github_user_id
- AGENTS.md: the ADB dump example creates its own mktemp file instead of
  assuming $SCRATCH
devSeedUserGithubToken can answer 200 with success:false (row not
upserted); the status code alone reported a seed that never landed.
A process that survives even SIGKILL kept its state file removed and the
stop reported success; the caller then released device claims over a live
server. stop now fails and keeps the state in that case.
- claim_port reaps an orphaned port claim (older than 120s with nothing
  listening) and the start path traps INT/TERM/HUP besides EXIT, so a
  killed start can no longer shrink the port pool permanently
- githubUserId derives deterministically from the account email: the
  upsert's setWhere only updates a matching row, so the fresh random id
  made every re-seed of an already-seeded account report success:false
  and tear the stub down with a misleading hint
- lsof ownership probes tolerate no-match exits; pipefail otherwise killed
  appium.sh before the fresh-start fallback could run
…ling

- appium startup adopts a port only on proven ownership (listener pid equals
  ours); an empty lsof keeps polling instead of adopting a vanished or
  foreign server
- github-stub signal traps exit instead of continuing a start that just
  released its port claim
- success:false with 200 and no error means an account seeded under an older
  random id — keep that usable row instead of tearing the stub down
success:false can come from an older random-id seed or from a real GitHub
connection on the dev account; the keep-message now says so instead of
implying only legacy seeds, and notes the stub accepts any token.
…lback

- a kept token row is verified to be the stub's (getUserAuthorization
  githubLogin == kilo-stub-user); a real GitHub connection row fails with a
  drop-the-row hint — under the stub its refresh bypasses
  GITHUB_API_BASE_URL, hits real github.com, and revokes itself
- start rolls back everything (session, env line, state, port claim) via one
  EXIT trap when it dies before the final state write, so an aborted start
  no longer blocks the next one behind 'session already runs'
- appium ownership checks degrade to pid+status when lsof is missing or
  cannot attribute the socket, instead of killing healthy servers across 50
  port blocks
- the row probe checks HTTP status and error envelope before advising: a
  failed probe reports 'could not determine the existing row' instead of
  confidently telling the operator to delete a row it never examined; a
  kept row must also be connected (a revoked stub row gets the drop hint)
- start's rollback undoes only what this run did (session-created and
  env-added flags), so a start losing the session-creation race cannot kill
  the winner's session, env line, or state
- the recorded-server reuse path applies the same blind-lsof rule as the
  start loop: adopt on pid+status after three blind probes instead of
  orphaning a healthy server and consuming a port block per invocation
- drop dead SEED_FAIL_MODE and the stale random-id comment
- github-stub creates its session under a pid-unique name armed before the
  create and renames to claim the canonical one: a signal at any instant
  tears down only our own session, and a lost rename race touches nothing
  but our port claim; the env-added flag arms before the append for the
  same reason
- the appium reuse probe rechecks pid liveness and /status on every pass,
  so a server dying mid-probe is cleaned up (stop_server) instead of
  adopted; a foreign listener still only drops our state
…windows

Concurrent stub commands raced over the session name, env line, and state
dir; each guard narrowed the window without closing it. The shared
process-lock (the appium.sh pattern) removes the class: one stub command per
worktree at a time, so the entry check is authoritative, the rollback flags
only order against signals, and the unique-name rename dance goes away.
Both row branches run after the account answered the seed call, so the
sign-in hint contradicted what the script had just proved.
- every network call under the stub lock carries --max-time 30, so a hung
  local API cannot hold the lock to its 300s wait bound; status runs
  unlocked (read-only) and answers during a long start
- a failed session create (a survivor of a SIGKILLed lock wrapper got there
  first) resets the created flag before exiting, so cleanup cannot touch
  the survivor's session
- align the env-append comment with the lock (the rename step it cited is
  gone)
pnpm dev:seed app:github-integration-copy <email> finds the newest real
integration in the shared dev database (fakes excluded: kilo-stub* logins
and far-future expiries — a real refresh token expires within months),
decrypts it with git-token-service's local dev key, re-encrypts for the
target account under a synthetic github_user_id (the pair index is unique),
and upserts. Scenarios that need a real integration (cloud agents) run
against the copy; with no valid donor the command fails with an explicit
BLOCKED message, and the runbook tells verifiers to report VERIFICATION
BLOCKED rather than fake or silently skip. Verified against the dev db:
copy mechanics end to end, and the blocked path on a database holding only
seeded rows.
- donor filter excludes 13-digit synthetic github_user_ids (stub seeds and
  earlier copies): revocation lands only on the real row, so a surviving
  copy could hand out dead credentials as a fresh success
- app:github-integration-copy --remove <email> deletes the account's token
  row, so an account can return to stub use; runbook and usage text now
  require separate accounts for stub and integration scenarios
- verified live: remove clears the copied row and is idempotent
One instruction per sentence in the integration and stub sections; no rule
changed meaning.
- the donor's access token is checked against api.github.com before the
  copy: database metadata cannot prove a rotated or GitHub-revoked token
  dead, so silent dead-credential copies reported success
- the copy row carries a dummy, already-expired refresh token: GitHub
  refresh tokens rotate on use, so a refreshing copy would kill the donor's
  stored refresh token and poison every later copy
- usage and runbook text updated to the access-token-only contract
The copy relocates real credentials, so every destructive path is now
guarded and every shared-grant hazard is stated:

- assertLocalDatabaseTarget: USE_PRODUCTION_DB=true and non-loopback hosts
  are refused, making the dev-only claim enforceable (verified live)
- --remove deletes only a synthetic row (copy or stub seed) and names what
  it removed; a real connection is refused with instructions
- a copy refuses to overwrite a real connection on the target account
- donor lookup also matches google_user_email (normalized_email is nullable)
- usage and runbook state the real blast radius: the copy shares the donor's
  OAuth grant, so disconnect on a copied account revokes the DONOR's
  authorization and no re-copy repairs it — disconnect scenarios are blocked
  on copied accounts; commit attribution carries a synthetic id
- stub status reports 'starting' when a session has no state yet, instead of
  claiming up for a stub that is not usable
- lock-bounded curls raised to 120s (30s was the budget this PR already
  found too small under full parallel load)
- a failed tmux create distinguishes a concurrent session from a tmux error
A dead database dumped a driver AggregateError stack trace, and a dead
network read as 'no valid donor'. Both now fail with one line naming the
command that fixes them, so an agent does not spend a round guessing at
ECONNREFUSED output or chasing a false BLOCKED. Verified live with postgres
stopped.
- the synthetic-only rule is re-asserted in the upsert's setWhere, so a real
  OAuth callback landing between the pre-check and the write cannot be
  overwritten; zero updated rows reports that explicitly (postgres predicate
  verified against synthetic and real id shapes)
- donors need >10 minutes of access-token life (twice git-token-service's
  5-minute refresh buffer): a token inside the buffer passes the live probe,
  then triggers a refresh the copy cannot perform
- the target lookup tries the exact sign-in email before the normalized
  fallback, since normalized_email is not unique
…dget

- the 'can never refresh' claim was wrong: git-token-service gates refresh on
  the ACCESS token's expiry, so the dummy refresh token IS sent and the copy
  ends up revoked. Docs, usage text and the runbook now describe that end
  state, the tool reports usableUntil, and donors need 35 minutes of access
  life so a copy is usable for at least 30
- --remove reports what actually went away (.returning()), so a row replaced
  mid-command cannot read as removed
- stub lock --wait raised to 900s, above the ~8 minute worst-case hold the
  120s per-call budgets allow, so a queued stop or seed waits instead of
  failing
- drop the now-unused 'or' import
The 35-minute SQL margin is measured before up to ten 15-second GitHub
probes, so a copy could return with less than the promised 30 usable
minutes. The selected donor is now re-checked against the clock after its
probe, and one helper owns the usable-until arithmetic the filter, the
check, and the report all share.
iscekic added 4 commits August 2, 2026 05:33
A campaign verifier died with no sentinel right after a doctor --json dump
(the whole environment, PATH included). The runbook now states the general
rule — bound anything that can print a lot — and names --json diagnostics
alongside the Appium and hierarchy cases it already covered.
@iscekic

iscekic commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

(bot) Kilobot review not detected after two retriggers, proceeding further

Comment thread dev/local/mobile-android.ts Outdated
Comment thread apps/mobile/e2e/appium.sh Outdated
Comment thread apps/mobile/e2e/github-stub.sh Outdated
Comment thread dev/seed/app/github-integration-copy.ts Outdated
Comment thread apps/mobile/e2e/github-stub.sh Outdated
Comment thread dev/seed/app/github-integration-copy.ts Outdated
Comment thread dev/local/tmux.ts Outdated
Comment thread apps/mobile/e2e/github-stub.sh Outdated
@iscekic

iscekic commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

(bot) Correction to the resolved Appium thread: direct inspection confirmed bare stop_server calls at lines 116, 119, and 170. Commit HEAD guards only those retry/stale-cleanup call sites; explicit server stop still fails when cleanup cannot stop the process. Added regression coverage.

Comment thread dev/local/appium-wrapper.test.ts Outdated
Comment thread dev/local/appium-wrapper.test.ts Outdated
Comment thread apps/mobile/e2e/github-stub.sh Outdated
Comment thread dev/local/appium-wrapper.test.ts Outdated
Comment thread dev/local/mobile-workflow.test.ts Outdated
Comment thread dev/local/mobile-workflow.test.ts Outdated
Comment thread dev/local/appium-wrapper.test.ts Outdated
Comment thread dev/local/appium-wrapper.test.ts Outdated
Comment thread dev/seed/app/github-integration-copy.ts Outdated
iscekic added 2 commits August 2, 2026 15:27
The e2e harness changes belong to #4960.
The assertions pin apps/mobile/e2e content that #4960 owns.
Point the seed help text at the stub that exists on main.
@iscekic
iscekic force-pushed the composer-wrap-fix-2ec5 branch from c370dd8 to 263407e Compare August 2, 2026 13:27
PR #4960 carries these changes and merges first. This branch keeps no copy.

- dev/local/mobile-android.ts
- dev/local/mobile-simulator.ts
- dev/local/tmux.ts
- dev/seed/app/github-integration-copy.ts
Comment thread dev/local/mobile-android.test.ts Outdated
assert.equal(readGradleWrapperVersion(root), '8.14.3');
});

test('lock contention on Android emulator launch throws the error message the retry guard exempts', async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WARNING: Leftover test from the reverted e2e harness — the retry guard it names no longer exists

chore(dev): revert shared e2e harness files (b23fabe) restored dev/local/mobile-android.ts to base, removing the emulator-start relaunch logic that exempted locked by another live process from retry. This test survived that revert, so:

  • Its name asserts a contract ("the error message the retry guard exempts") that no code in dev/local/mobile-android.ts implements anymore — a future reader will believe the emulator relaunch policy is covered when it is not.
  • It exercises only withProcessLockAsync from ./process-lock, so it belongs in a process-lock test, not in mobile-android.test.ts; the new import { withProcessLockAsync } from './process-lock'; on line 21 exists solely for it.
  • const holder = await withProcessLockAsync(...) on line 379 binds a value that is never read (the callback returns void), which is dead code even though dev/ is outside the pnpm lint and pnpm typecheck scopes.

Given the PR is now scoped to the mobile composer width fix, dropping this test and its import returns dev/local/mobile-android.test.ts to base along with the rest of the harness revert.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

The reverted e2e harness removed the emulator-start retry guard, so the test
name in mobile-android.test.ts was wrong. The test covers process-lock, not
Android. Move it to process-lock.test.ts and rename it.
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.

1 participant