Skip to content

Commit 14a8b54

Browse files
authored
test(term-fidelity): opencode readiness hardening + db hygiene (#418)
Harness-only. (1) XDG_DATA_HOME isolation for the opencode matrix leg so tests use a throwaway session db instead of the user's real ~/.local/share/opencode/opencode.db (~3.2GB) — stops the matrix polluting real opencode history and removes a boot-contention source. (2) Opencode-only boot re-nudge: re-submits the readiness prompt once the home screen renders if the broker's spawn-time task injection landed before OpenTUI input was live (the injection/boot race pear-417 root-caused). Both changes are gated on cli==='opencode' — claude/codex/grok readiness paths untouched. Eliminated the readiness flake (28/28 vs ~1/8 pre-fix). Independently reviewed + CI green (checks/playwright/packaged-mcp-smoke).
1 parent 06137e1 commit 14a8b54

2 files changed

Lines changed: 74 additions & 5 deletions

File tree

tests/term-fidelity/harness.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { _electron as electron, type ElectronApplication, type Page } from 'playwright'
22
import { HarnessDriverClient } from '@agent-relay/harness-driver'
3-
import { mkdir, mkdtemp, readFile, realpath, rm } from 'node:fs/promises'
3+
import { copyFile, mkdir, mkdtemp, readFile, realpath, rm } from 'node:fs/promises'
44
import { createServer } from 'node:net'
55
import { homedir } from 'node:os'
66
import { basename, join, resolve } from 'node:path'
@@ -208,6 +208,29 @@ export async function launchFidelityHarness(
208208
const instanceName = `term-fidelity-${cli}-${process.pid}-${basename(runRoot).slice(-6)}`
209209
if (instanceName === 'pear') throw new Error('Refusing to use the live broker instance name')
210210

211+
// OpenCode keeps its session history in a single SQLite DB under
212+
// XDG_DATA_HOME/opencode. By default that resolves to the user's real,
213+
// shared ~/.local/share/opencode/opencode.db, which the live app's
214+
// ai-history sync opens read/write. When that sync's WAL activity coincides
215+
// with the harness agent's boot write, OpenCode's first paint blocks and it
216+
// renders an empty frame that never reaches readiness — an intermittent
217+
// TF_OPENCODE_READY timeout unrelated to the renderer under test. Give
218+
// OpenCode an isolated, empty data dir so its boot never contends with the
219+
// shared DB; auth lives in the same dir, so copy the real auth.json across.
220+
// The model cache stays in XDG_CACHE_HOME and is untouched.
221+
const opencodeDataHome = cli === 'opencode' ? join(runRoot, 'xdg-data') : null
222+
if (opencodeDataHome) {
223+
await mkdir(join(opencodeDataHome, 'opencode'), { recursive: true })
224+
const sourceDataHome = process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share')
225+
await copyFile(
226+
join(sourceDataHome, 'opencode', 'auth.json'),
227+
join(opencodeDataHome, 'opencode', 'auth.json')
228+
).catch(() => {
229+
// No auth.json (env-key auth, or never logged in): OpenCode falls back to
230+
// its other credential sources. A fresh empty data dir is still correct.
231+
})
232+
}
233+
211234
let broker: HarnessDriverClient | null = null
212235
let electronApp: ElectronApplication | null = null
213236
try {
@@ -221,7 +244,10 @@ export async function launchFidelityHarness(
221244
OPENCODE_CONFIG_CONTENT: JSON.stringify({
222245
autoupdate: false,
223246
permission: { bash: 'ask', external_directory: 'ask' }
224-
})
247+
}),
248+
// Isolate OpenCode's session DB (see opencodeDataHome above). Only set
249+
// for the OpenCode matrix leg so other CLIs' data dirs are unaffected.
250+
...(opencodeDataHome ? { XDG_DATA_HOME: opencodeDataHome } : {})
225251
}
226252
broker = await spawnBrokerWithoutInheritedIdentity({
227253
cwd: projectRoot,

tests/term-fidelity/workloads.ts

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,19 @@ import {
1111
const MARKER_TIMEOUT_MS = 5 * 60_000
1212
const AUTOMATIC_PERMISSION_MODE = /(?:bypass permissions|always approve|auto[- ]?approve|auto mode|plan mode|don'?t ask|yolo mode|full access \(current\)).*(?:on|enabled)?/iu
1313

14+
// OpenCode's OpenTUI can take several seconds to accept input after launch
15+
// (MCP init, filesystem watcher / location services, and a scan of the user's
16+
// global skill + config trees). The broker injects the readiness task once at
17+
// spawn; an injection that lands before OpenCode's input is live is silently
18+
// dropped, and OpenCode then idles at its home screen until the readiness
19+
// deadline. These bound an OpenCode-only boot re-nudge that re-submits the
20+
// readiness prompt once the home screen is up and the marker still hasn't
21+
// appeared. HOME_HINT matches only OpenCode's idle home screen, so the nudge
22+
// never fires mid-response; GRACE first lets the broker's own injection land.
23+
const OPENCODE_HOME_HINT = /ask anything|opencode zen|ctrl\+p/iu
24+
const READINESS_NUDGE_GRACE_MS = 6_000
25+
const READINESS_NUDGE_INTERVAL_MS = 8_000
26+
1427
interface SpawnedAgent {
1528
name: string
1629
terminal: Locator
@@ -93,10 +106,16 @@ async function acceptWorkspaceTrustIfShown(
93106
harness: FidelityHarness,
94107
terminal: Locator,
95108
agentName: string,
96-
marker: string
109+
marker: string,
110+
// OpenCode only: the readiness prompt to re-submit if the broker's spawn-time
111+
// injection was dropped by a not-yet-live OpenTUI input. Leave undefined for
112+
// CLIs whose readiness injection is reliable so their paths are untouched.
113+
readinessNudge?: string
97114
): Promise<void> {
98115
const deadline = Date.now() + 90_000
116+
const startedAt = Date.now()
99117
let acceptedTrust = false
118+
let lastNudgeAt = 0
100119
let lastScreen = ''
101120
while (Date.now() < deadline) {
102121
try {
@@ -116,6 +135,21 @@ async function acceptWorkspaceTrustIfShown(
116135
) {
117136
throw new Error(`${harness.cli} is not authenticated:\n${lastScreen}`)
118137
}
138+
// Boot re-nudge (see READINESS_NUDGE_* above): only once the home screen
139+
// is up, the marker is still absent, the broker's own injection has had
140+
// its grace window, and the last nudge has drained. Re-submitting the
141+
// idempotent readiness prompt lets a dropped spawn-time injection self-
142+
// heal. This runs strictly before any workload and stops the instant the
143+
// marker appears, so it cannot bleed into the workload it precedes.
144+
if (
145+
readinessNudge &&
146+
OPENCODE_HOME_HINT.test(lastScreen) &&
147+
Date.now() - startedAt >= READINESS_NUDGE_GRACE_MS &&
148+
Date.now() - lastNudgeAt >= READINESS_NUDGE_INTERVAL_MS
149+
) {
150+
await submitPrompt(terminal, readinessNudge)
151+
lastNudgeAt = Date.now()
152+
}
119153
} catch (error) {
120154
if (error instanceof Error && error.message.includes('not authenticated')) throw error
121155
}
@@ -152,6 +186,7 @@ export async function spawnRealAgent(harness: FidelityHarness): Promise<SpawnedA
152186

153187
const requestedName = `tf-${harness.cli}`
154188
const marker = `TF_${harness.cli.toUpperCase()}_READY`
189+
const readinessTask = `Reply with exactly one token made from the parts "TF", "${harness.cli.toUpperCase()}", "READY", joined with one underscore between adjacent parts. Do not use tools.`
155190
const spawned = await harness.page.evaluate(async ({ projectId, root, cli, name, task, args }) => {
156191
const api = (window as unknown as Window & {
157192
pear: {
@@ -180,7 +215,7 @@ export async function spawnRealAgent(harness: FidelityHarness): Promise<SpawnedA
180215
root: harness.projectRoot,
181216
cli: harness.cli,
182217
name: requestedName,
183-
task: `Reply with exactly one token made from the parts "TF", "${harness.cli.toUpperCase()}", "READY", joined with one underscore between adjacent parts. Do not use tools.`,
218+
task: readinessTask,
184219
args: initialArgs(harness.cli)
185220
})
186221
const agentName = spawned.name || requestedName
@@ -199,7 +234,15 @@ export async function spawnRealAgent(harness: FidelityHarness): Promise<SpawnedA
199234
{ message: `live xterm runtime should mount for ${agentName}`, timeout: 60_000 }
200235
).toBeGreaterThan(0)
201236

202-
await acceptWorkspaceTrustIfShown(harness, terminal, agentName, marker)
237+
await acceptWorkspaceTrustIfShown(
238+
harness,
239+
terminal,
240+
agentName,
241+
marker,
242+
// OpenCode's slow OpenTUI boot can drop the broker's spawn-time readiness
243+
// injection; re-nudge it. Other CLIs inject reliably, so leave them alone.
244+
harness.cli === 'opencode' ? readinessTask : undefined
245+
)
203246
if (harness.cli === 'claude') {
204247
// Normalize while the startup status is still visible. After workload 1,
205248
// the bypass badge can scroll out even though the mode remains active.

0 commit comments

Comments
 (0)