fix(opencode): keep overlay manifest cleanup inside owned directories (#24763)

* fix: wait for OpenCode worker composer before first dispatch

Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors.

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>

* feat(opencode): probe execution-host CLI capabilities

* fix(opencode): select plugin default for execution host loader

* fix(opencode): limit prompt prefill capability to verified release

* feat(opencode): probe launch capabilities on the execution host

* fix(opencode): select plugin loader for the launched host binary

* fix(opencode): match WSL probe cwd and declared guest environment

* fix(opencode): preserve launch environment deletion boundaries

* wip(opencode): authorize native startup prompt intent at execution owner

* fix(opencode): atomically replace status plugin entrypoints

* fix(opencode): retain plugin permissions across restrictive umasks

* test(opencode): resolve permission fixture from primary cwd

* feat(opencode): install startup prompt plugin independently of status hooks

* fix(opencode): wait for admitted startup intent and preserve failed-launch briefs

* fix(opencode): confine overlay manifest cleanup to owned directories

Co-authored-by: Adnan Khan <adnank11427@gmail.com>

* fix: wait for OpenCode worker composer before first dispatch

Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors.

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>

* feat(opencode): probe execution-host CLI capabilities

* fix(opencode): select plugin default for execution host loader

* fix(opencode): limit prompt prefill capability to verified release

* feat(opencode): probe launch capabilities on the execution host

* fix(opencode): select plugin loader for the launched host binary

* fix(opencode): match WSL probe cwd and declared guest environment

* fix(opencode): preserve launch environment deletion boundaries

* wip(opencode): authorize native startup prompt intent at execution owner

* fix(opencode): atomically replace status plugin entrypoints

* fix(opencode): retain plugin permissions across restrictive umasks

* test(opencode): resolve permission fixture from primary cwd

* feat(opencode): install startup prompt plugin independently of status hooks

* fix(opencode): wait for admitted startup intent and preserve failed-launch briefs

* fix(opencode): unsubscribe hook settings during async host shutdown

* STRICT launch CI contract correction

* CAPS launch CI contract correction

* INTENT launch CI contract correction

* test: initialize Claude prompt state in output retention fixture

* Wait for OpenCode location hydration in intent startup

* Bind OpenCode startup readiness to the current location in intent startup

---------

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com>
Co-authored-by: Adnan Khan <adnank11427@gmail.com>
Co-authored-by: Orca startup hydration review <agents@stably.ai>
This commit is contained in:
Neil
2026-10-04 03:15:12 -07:00
committed by GitHub
co-authored by Brennan Benson Adnan Khan Ahmed Nagy Orca startup hydration review
parent d9173ffbdb
commit 8c617301f7
5 changed files with 202 additions and 6 deletions
@@ -0,0 +1,98 @@
import {
mkdirSync,
mkdtempSync,
readFileSync,
readdirSync,
rmSync,
symlinkSync,
writeFileSync
} from 'node:fs'
import { tmpdir } from 'node:os'
import { basename, dirname, join } from 'node:path'
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
import { setAppEnvironment } from '../../shared/app-environment'
import { safeRemoveTree } from '../pty/overlay-mirror'
import { OpenCodeHookService } from './hook-service'
import { OPENCODE_OVERLAY_MANIFEST_FILE } from './opencode-overlay-manifest'
let root: string
let source: string
let service: OpenCodeHookService
beforeEach(() => {
root = mkdtempSync(join(tmpdir(), 'orca-overlay-confinement-'))
source = join(root, 'source')
mkdirSync(join(source, 'plugins'), { recursive: true })
writeFileSync(join(source, 'plugins', 'user.js'), 'export default {}')
vi.stubEnv('XDG_CONFIG_HOME', join(root, 'xdg'))
setAppEnvironment({
getPath: () => join(root, 'profile'),
getAppPath: () => process.cwd(),
getVersion: () => '0.0.0-test',
isPackaged: () => false,
onWillQuit: () => {},
exit: () => {},
getAppMetrics: () => []
})
service = new OpenCodeHookService({
pluginFileName: 'orca-test.js',
legacyHooksDir: 'legacy-test',
overlayDir: 'overlay-test',
pluginSource: () => 'export default {}'
})
})
afterEach(() => {
vi.unstubAllEnvs()
rmSync(root, { recursive: true, force: true })
})
it.each(['overlay-root', 'source-overlay', 'plugins'] as const)(
'preserves outside files when the owned %s is replaced by a directory link',
(boundary) => {
const overlay = service.buildPtyEnv('pane-1', source).OPENCODE_CONFIG_DIR
if (!overlay) {
throw new Error('Expected an isolated overlay')
}
const outside = join(root, 'outside')
const externalOverlay = boundary === 'overlay-root' ? join(outside, basename(overlay)) : outside
mkdirSync(join(externalOverlay, 'plugins'), { recursive: true })
const sentinel = join(externalOverlay, 'plugins', 'keep.js')
writeFileSync(sentinel, 'export const keep = true')
writeFileSync(
join(externalOverlay, OPENCODE_OVERLAY_MANIFEST_FILE),
JSON.stringify({ topLevelEntries: [], pluginEntries: ['keep.js'] })
)
const replaced =
boundary === 'overlay-root'
? dirname(overlay)
: boundary === 'source-overlay'
? overlay
: join(overlay, 'plugins')
const target = boundary === 'plugins' ? join(outside, 'plugins') : outside
if (boundary === 'plugins') {
writeFileSync(
join(overlay, OPENCODE_OVERLAY_MANIFEST_FILE),
JSON.stringify({ topLevelEntries: [], pluginEntries: ['keep.js'] })
)
}
safeRemoveTree(replaced)
symlinkSync(target, replaced, process.platform === 'win32' ? 'junction' : 'dir')
const before = readdirSync(outside, { recursive: true }).toSorted()
const result = service.buildPtyEnv('pane-2', source)
expect(readFileSync(sentinel, 'utf8')).toBe('export const keep = true')
expect(readdirSync(outside, { recursive: true }).toSorted()).toEqual(before)
expect(result).toEqual({ OPENCODE_CONFIG_DIR: source })
}
)
it('still removes a stale mirrored entry from a real owned overlay', () => {
const overlay = service.buildPtyEnv('pane-1', source).OPENCODE_CONFIG_DIR
if (!overlay) {
throw new Error('Expected an isolated overlay')
}
rmSync(join(source, 'plugins', 'user.js'))
expect(service.buildPtyEnv('pane-2', source)).toEqual({ OPENCODE_CONFIG_DIR: overlay })
expect(readdirSync(join(overlay, 'plugins'))).toEqual(['orca-test.js'])
})
+10 -4
View File
@@ -11,7 +11,7 @@ import {
writeFileSync
} from 'node:fs'
import { createHash } from 'node:crypto'
import { isSafeDescendCandidate, mirrorEntry, safeRemoveTree } from '../pty/overlay-mirror'
import { isSafeDescendCandidate, mirrorEntry, safeRemoveOverlay } from '../pty/overlay-mirror'
import {
getOpenCode2PluginSource,
getOpenCodeFamilyPluginSource,
@@ -141,7 +141,13 @@ export class OpenCodeHookService {
}
const overlayDir = this.getSourceOverlayDir(existingConfigDir)
try {
mkdirSync(overlayDir, { recursive: true })
// Owned directories must stay real; a replaced parent redirects both cleanup and writes.
for (const directory of [this.getOverlayRoot(), overlayDir, join(overlayDir, 'plugins')]) {
mkdirSync(directory, { recursive: true })
if (!isSafeDescendCandidate(lstatSync(directory))) {
return { OPENCODE_CONFIG_DIR: existingConfigDir }
}
}
if (existsSync(existingConfigDir)) {
this.mirrorUserConfig(existingConfigDir, overlayDir)
}
@@ -230,7 +236,7 @@ export class OpenCodeHookService {
private clearManifestEntries(overlayDir: string, manifest: OpenCodeOverlayManifest): void {
for (const entryName of manifest.topLevelEntries) {
safeRemoveTree(join(overlayDir, entryName))
safeRemoveOverlay(join(overlayDir, entryName), overlayDir)
}
const overlayPluginsDir = join(overlayDir, 'plugins')
@@ -238,7 +244,7 @@ export class OpenCodeHookService {
if (entryName === this.pluginFileName) {
continue
}
safeRemoveTree(join(overlayPluginsDir, entryName))
safeRemoveOverlay(join(overlayPluginsDir, entryName), overlayPluginsDir)
}
}
@@ -0,0 +1,32 @@
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { expect, it } from 'vitest'
import {
readOpenCodeOverlayManifest,
OPENCODE_OVERLAY_MANIFEST_FILE
} from './opencode-overlay-manifest'
it('accepts only string manifest entries and tolerates invalid persisted shapes', () => {
const root = mkdtempSync(join(tmpdir(), 'orca-overlay-manifest-'))
try {
const path = join(root, OPENCODE_OVERLAY_MANIFEST_FILE)
for (const text of ['null', '1', '{']) {
writeFileSync(path, text)
expect(readOpenCodeOverlayManifest(root)).toEqual({ topLevelEntries: [], pluginEntries: [] })
}
writeFileSync(
path,
JSON.stringify({
topLevelEntries: ['valid', 1, null],
pluginEntries: [{ bad: true }, 'plugin.js']
})
)
expect(readOpenCodeOverlayManifest(root)).toEqual({
topLevelEntries: ['valid'],
pluginEntries: ['plugin.js']
})
} finally {
rmSync(root, { recursive: true, force: true })
}
})