Repository navigation
Description
Description
On a cold launch in a large repo, the TUI's first frame takes 14.6 s. The devtools bar (debug.devtools: true, debug.timing: true) reads Time to first draw: 14589.25ms. Opening the TUI against an already-running service on the same machine connects in 122 ms.
Most of the wait is the per-launch managed service, not rendering:
+0to+10.20 s: the TUI spawnsopencode serve --service; the child logscli starting, spends 3.89 s with no further log line, then 5.30 s inlocation services booted(4.21 s of that after watcher setup).+10.56 sto+12.56 s: the first event-stream attempt times out after 2.0 s.+14.59 s: first frame.
The frame lands before the event-stream retry (+15.45 s), so the 2.9 s backoff is not the direct cost, but the frame is still 2.0 s after the timeout.
Plugins
None configured: no plugin key in opencode.jsonc and no local plugin directories. The service logs plugins=14 during reconciliation, so those are built-ins.
OpenCode version
2.2.0, channel latest, local=false, standalone install at ~/.opencode/bin/opencode.
Steps to reproduce
- Set
debug.devtools: trueanddebug.timing: true. - Open the TUI in a large repo, then close it. The managed service receives SIGTERM (
shutdown started signal=SIGTERM). - Open the TUI in the same directory again.
- Read
Time to first drawin the devtools bar.
Cold starts only. A warm attach to an already-running service connects in 0.12 s.
Screenshot
The devtools overlay from this launch reads:
Time to first draw: 14589.25ms
FPS: 55
Frame: 23.648ms (avg: 19.398ms)
Render: 0.034ms (avg: 0.044ms)
Memory: 50.83MB
Threaded: No
Operating System
Pop!_OS 24.04, kernel 7.2.6-76070206-generic, x86_64.
Terminal
Ghostty (TERM_PROGRAM=ghostty, TERM=xterm-256color, COLORTERM=truecolor), shell /bin/bash.
Additional context
Timeline for the 2026-10-27 11:10 launch (run ids shortened, project path redacted):
| wall clock | from TUI start | event |
|---|---|---|
| 11:10:12.197 | 0 | cli starting (TUI, args=[]) |
| 11:10:13.163 | +0.97 s | background service starting reason=missing |
| 11:10:13.258 | +1.06 s | child cli starting (serve --service) |
| 11:10:17.149 | +4.95 s | first server log line (git rev-parse); 3.89 s after the child started, nothing logged in between |
| 11:10:18.15 | +5.95 s | watchers start for /etc, /, /home, ~, ~/development, ~/development/<repo> |
| 11:10:22.395 | +10.20 s | location services booted durationMs=5295 |
| 11:10:22.760 | +10.56 s | event-stream connect attempt 0 |
| 11:10:24.761 | +12.56 s | Timed out connecting to server (2.0 s timeout) |
| 11:10:26.786 | +14.59 s | first frame, derived from the 14,589.25 ms metric |
| 11:10:27.650 | +15.45 s | event-stream retry |
| 11:10:27.804 | +15.61 s | event stream connected |
Log excerpt:
11:10:12.197 cli starting args=[] version=2.2.0
11:10:13.163 background service starting reason=missing
11:10:13.258 cli starting args=["serve","--service"]
11:10:17.149 spawning process command=git ... (3.89 s silent gap)
11:10:18.154 watcher subscribe path=/etc type=entries ignores=0
11:10:18.155 watcher subscribe path=/ type=entries ignores=0
11:10:18.155 watcher subscribe path=/home type=entries ignores=0
11:10:18.156 watcher subscribe path=~ type=entries ignores=0
11:10:18.156 watcher subscribe path=~/development type=entries ignores=0
11:10:18.157 watcher subscribe path=~/development/<repo> type=entries ignores=0
11:10:22.395 location services booted durationMs=5295 (4.21 s silent gap)
11:10:22.760 event stream connecting attempt=0
11:10:24.761 event stream disconnected error="Timed out connecting to server"
11:10:27.650 event stream connecting attempt=1
11:10:27.804 event stream connected
Time to first drawis rendered fromperformance.now()on first render, but it is not written to the log. The 14,589.25 ms comes from the devtools bar; the table is reconstructed from the server log.- Service boot is usually fast. Across 282
location services bootedentries in the 12-day log: 255 under 250 ms, 16 between 250 ms and 1 s, 7 between 1 and 2 s, 1 between 2 and 4 s, 3 over 4 s (5.96 s, 5.30 s, 4.52 s). The slow ones are cold launches. - Four MCP servers are configured (three remote, one local). They connect at +17 s and later, so they are not part of the 14.6 s.
- Related: TUI blocks its first content on a 4.5MB provider payload; render the shell and populate async #41078 (v1, closed during the v1 to v2 triage) covers the TUI blocking its first content on the provider catalog. The PRs that addressed that path on
dev(fix(tui): load the provider catalog only when a connect dialog opens #53593, fix(tui): stop blocking first render on the full provider catalog #53680, feat(tui): paint an editable prompt before the TUI loads #53698) were closed unmerged. core: cold VCS location acquisition blocked the service for 53 seconds #37794 is open and asks for narrower timing inside location boot; the two silent gaps above are the same shape.
The open questions are what runs during the two silent gaps and what gates the frame between location services booted and the first render. Instrumenting both gaps would make the boot budget visible; drawing the shell before the service is ready would remove the 14.6 s empty terminal.
Activity
opencode-agent commentedon Oct 27, 2026
Thanks for the detailed timeline. I tried to reproduce this on 2.2.0 (Linux x86_64, no plugins), with the managed service stopped before each launch and a git repo of about 150k files at
~/development/repo. I couldn't get the slow boot:location services booted durationMs=55, and the event stream connected on the first attempt about 1.7 s after launch.location services booted durationMs=97. The event stream again connected on the first attempt, with noTimed out connecting to server.I didn't see either silent gap in the service. With a cold page cache, the one slow step I saw was in the TUI process itself: about 5 s between
cli startingandbackground service starting.Your log has the slow boots on only 3 of 282 launches, so this seems to depend on something on your machine. These would help:
--log-level debugservice log from a slow cold launch, especially the lines inside the two gaps (cli starting→ firstgitspawn, and watcher subscribe →location services booted).~, and whether the disk is an SSD or a spinning HDD, local or on a network or encrypted volume.sync; echo 3 | sudo tee /proc/sys/vm/drop_cachesbefore launching.Related issues, most likely first:
$HOMEand/, which can starve the event loopAddin commentedon Oct 27, 2026
@opencode-agent
Consolidating the earlier data and the follow-up tests into one answer.
Storage and files
opencodebinary and the SQLite store are on/dev/sdb: a Sabrent0bda:9210RTL9210 M.2 NVMe bridge,crypto_LUKS-> LVM -> ext4 (220 GB, 75% used).2109:0817, 5000M on Bus 002), but the SSD is attached through its USB 2.0 companion (2109:2817, Bus 001) and enumerates at 480 Mbps withbcdUSB 2.10. The hub is 3.0; the SSD link is 2.0 (cable or adapter). At 480 Mbps a 236 MB cold read is roughly 4-6 s, the size of the first gap.node_modules). Home: 1,571,054 files;find ~ -xdev -type ftook 67.5 s (3.9 s user, 9.3 s sys, the rest I/O).The slow launch was the first after a reboot
uptime -sreports the boot at 12:08:15 local (11:08:15Z). The log shows the previous TUI's service getting SIGTERM at 11:07:53Z and the next TUI starting at 11:10:12Z: the 14,589.25 ms draw ran with a cold page cache, 1m57s after boot, right after the 2.2.0 upgrade. The other two slow boots (Oct 20 15:50, Oct 21 08:27) are also first launches after long idle. Most warm launches are 30-250 ms. A plain reboot on 13:38:12 gave 3478.41 ms (below), so the 14.6 s is the post-upgrade case, not the steady cold-boot cost.drop_caches + service restart: 373 ms, no repro
/proc/vmstatshowsdrop_pagecache 1, so the drop ran. A new service came up at 12:03:51.982Z and reachedlocation services bootedat 12:03:52.593Z (373 ms); the TUI reconnected in 0.6 s. No 3.89 s or 4.21 s gap. This is not a true cold boot: the running service kept the 236 MB binary mapped (mapped pages are not evicted), and dirty SQLite pages are not evicted either. A real cold boot needs the service to stop before the drop, or a reboot.First-frame gate (TUI-side probe, 2.2.0, warm service)
--server http://127.0.0.1:1): the CLI exits withCould not reach server ... ECONNREFUSED. There is no shell-first path at startup; the "Connection lost" overlay only covers a disconnect after the TUI is up.update check12:23:49.384Z ->event stream connecting50.140 ->connected50.162 -> plugin reconciliation id=1 50.199-50.225 (26 ms, 14 plugins) -> first draw ~50.3 -> reconciliation id=2 50.319.Post-reboot re-test
cli starting12:40:15.935Z -> firstgit17.346 (+1.41 s) ->location services booted17.977 (664 ms). TUI event stream connected 19.420 (+4.03 s); no timeout or retry.9fd09e2d). The metric latches per bar instance, not per process, so it grows after the first draw; the 14.6 s screenshot may also be a later mount.Debug log
After the reboot the fresh TUI spawn did apply the env: the
serve --servicepid hasOPENCODE_LOG_LEVEL=debugin/proc/<pid>/environand logslevel=DEBUG(Skipped models.dev APIs...,skill source loaded). The first DEBUG line is at 19.076, afterlocation services booted, so the 1.41 s and 0.66 s gaps are still empty at debug level. They are not log-instrumented;strace -T -fon the service start is the remaining way to see them.The earlier
service restartpath (TUI still running) did not apply the env: the respawned child had noOPENCODE_LOG_LEVELand no DEBUG lines, and--log-level debugnever reaches the child. That is #59610.strace
Two traces, both warm:
serve: execve ->database schema bootstrap0.42 s; every traced syscall under 1 ms.serve --servicewith the TUI connecting: execve 13:50:26.767 -> DB open 27.160 (0.39 s); the location boot 58.235 -> 58.351 (125 ms), 279 syscalls, 53getdents64. The largestgetdents64returned 147 entries (the top-level home), so this is not a recursive 1.57 M-file walk. The slowest syscall was 2.1 ms.There is no blocking syscall and no syscall-volume explosion. The cold 3.89 s and 4.21 s are consistent with the same small set of opens,
getdents64, git children and config lookups at cold-disk latency (480 Mbps + LUKS) plus page faults, which strace does not trace. A cold trace needs the service stopped and the cache dropped (or a reboot) before the traced start.Repro
Reboot, then start the TUI once. The plain-reboot re-test gave 3478.41 ms (service boot 664 ms), not 14.6 s; the 14.6 s case followed the 2.2.0 upgrade. The bar then re-mounts and shows 5478.41 ms, so the metric overstates the first draw. The software fix is to draw the shell before the service is ready.