Skip to content

Commit 34f65e5

Browse files
committed
fix(registry): prevent race conditions in registry file handling
Replace async file operations with sync ones during registry file creation to ensure atomicity Add validation for empty/invalid registry files to handle edge cases
1 parent aa49bdc commit 34f65e5

File tree

2 files changed

+35
-6
lines changed

2 files changed

+35
-6
lines changed

src/agents/monitoring/registry.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,21 @@ export class AgentRegistry {
4646
try {
4747
if (existsSync(this.registryPath)) {
4848
const content = await readFile(this.registryPath, 'utf-8');
49+
50+
// Skip empty or whitespace-only files (race condition during file creation)
51+
if (!content || content.trim().length === 0) {
52+
logger.debug('Registry file is empty, skipping reload');
53+
return;
54+
}
55+
4956
const parsed = JSON.parse(content) as AgentRegistryData;
57+
58+
// Validate parsed data structure
59+
if (!parsed || typeof parsed !== 'object' || !parsed.agents) {
60+
logger.warn('Registry file has invalid structure, skipping reload');
61+
return;
62+
}
63+
5064
this.data = parsed;
5165
logger.debug(`Async reloaded agent registry with ${Object.keys(parsed.agents).length} agents`);
5266
}
@@ -63,7 +77,21 @@ export class AgentRegistry {
6377
try {
6478
if (existsSync(this.registryPath)) {
6579
const content = readFileSync(this.registryPath, 'utf-8');
80+
81+
// Skip empty or whitespace-only files (race condition during file creation)
82+
if (!content || content.trim().length === 0) {
83+
logger.debug('Registry file is empty, returning default registry');
84+
return { lastId: 0, agents: {} };
85+
}
86+
6687
const parsed = JSON.parse(content) as AgentRegistryData;
88+
89+
// Validate parsed data structure
90+
if (!parsed || typeof parsed !== 'object' || !parsed.agents) {
91+
logger.warn('Registry file has invalid structure, returning default registry');
92+
return { lastId: 0, agents: {} };
93+
}
94+
6795
logger.debug(`Loaded agent registry with ${Object.keys(parsed.agents).length} agents`);
6896
return parsed;
6997
}

src/agents/monitoring/registryLock.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,19 @@ export class RegistryLockService {
2929
const lockPath = this.registryPath;
3030

3131
// Ensure file exists before locking (proper-lockfile requires this)
32-
// Create empty file if it doesn't exist
33-
const { existsSync } = await import('fs');
34-
const { writeFile, mkdir } = await import('fs/promises');
32+
// Use synchronous operations to prevent race conditions during file creation
33+
const { existsSync, mkdirSync, writeFileSync } = await import('fs');
3534
const { dirname } = await import('path');
3635

3736
if (!existsSync(lockPath)) {
3837
const dir = dirname(lockPath);
3938
if (!existsSync(dir)) {
40-
await mkdir(dir, { recursive: true });
39+
mkdirSync(dir, { recursive: true });
4140
}
42-
// Create minimal valid registry file
43-
await writeFile(lockPath, JSON.stringify({ lastId: 0, agents: {} }, null, 2), 'utf-8');
41+
// Create minimal valid registry file synchronously
42+
// This ensures the file is fully written before we try to lock it
43+
const initialData = JSON.stringify({ lastId: 0, agents: {} }, null, 2);
44+
writeFileSync(lockPath, initialData, 'utf-8');
4445
logger.debug(`Created registry file for locking: ${lockPath}`);
4546
}
4647

0 commit comments

Comments
 (0)