Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions packages/opencode/src/custody-manifest-watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@ function registry(): WatcherRegistry {
}

function filenameMatches(
filename: string | Buffer | null,
filename: string | Buffer | null | undefined,
expected: string,
): boolean {
return filename === null || filename.toString() === expected
// Node may omit the filename (undefined) when it cannot attribute the change
// to a specific entry; treat that like null and reconcile the whole directory.
return filename == null || filename.toString() === expected
}

function schedule(entry: WatcherEntry): void {
Expand Down
33 changes: 33 additions & 0 deletions packages/opencode/src/tests/custody-manifest-watcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,39 @@ describe('custody manifest watcher registry', () => {
expect(closeCalls).toBe(1)
})

test('treats an undefined filename as a directory-level change', async () => {
let fire:
| ((event: string, filename: string | undefined) => void)
| undefined
const watcher = Object.assign(new EventEmitter(), {
close() {},
}) as FSWatcher
const watchImpl = ((_path, _options, listener) => {
fire = listener as typeof fire
return watcher
}) as typeof watch
let calls = 0
const adoption = adoptCustodyManifestWatcher(
'/tmp/custody-watch/undefined-filename.json',
() => {
calls += 1
},
{ watchImpl, debounceMs: 1 },
)

try {
await Bun.sleep(5)
const baseline = calls
// Node may omit the filename entirely; the watcher must reconcile
// rather than throw on the missing value.
expect(() => fire?.('change', undefined)).not.toThrow()
await Bun.sleep(5)
expect(calls).toBeGreaterThan(baseline)
} finally {
adoption.release()
}
})

test('polling observes replacement when fs.watch is unavailable', async () => {
const directory = await mkdtemp(join(tmpdir(), 'custody-watch-'))
const path = join(directory, 'handles.json')
Expand Down
Loading