mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-04 22:49:41 +02:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17a976fa2e |
+5
-1
@@ -1031,7 +1031,11 @@ export async function ensureStatusLineExporterScript(): Promise<string> {
|
||||
// render, and a truncate-then-write (plus a chmod AFTER the write) opened two
|
||||
// windows in which Claude Code could run an empty or non-executable file.
|
||||
// rename() swaps the complete, already-executable file in atomically.
|
||||
const tmpPath = `${scriptPath}.${process.pid}.${Date.now()}.tmp`;
|
||||
// ⚠️ The temp name must be unique per CALL, not per millisecond: sessions created
|
||||
// concurrently (spawn_workers, a multi-tab Run) refresh this together, a shared
|
||||
// name let the first rename consume the others' temp file, and their ENOENT
|
||||
// dropped those sessions from tmux to the direct-PTY fallback.
|
||||
const tmpPath = `${scriptPath}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`;
|
||||
await writeFile(tmpPath, desired);
|
||||
await chmod(tmpPath, 0o755);
|
||||
await rename(tmpPath, scriptPath);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* hook definitions for desktop notifications.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll, beforeEach, afterAll, afterEach } from 'vitest';
|
||||
import { describe, it, expect, beforeAll, beforeEach, afterAll, afterEach, vi } from 'vitest';
|
||||
import {
|
||||
chmodSync,
|
||||
closeSync,
|
||||
@@ -26,6 +26,7 @@ import { spawn } from 'node:child_process';
|
||||
import {
|
||||
applyStatusLineConfig,
|
||||
ensureCodemanHooks,
|
||||
ensureStatusLineExporterScript,
|
||||
findEffectiveUserStatusLineCommand,
|
||||
generateBackgroundWakeScript,
|
||||
generateHooksConfig,
|
||||
@@ -1418,6 +1419,26 @@ describe('resolveStatusLineCliCommand', () => {
|
||||
expect(siblings).toEqual([]);
|
||||
});
|
||||
|
||||
it('survives concurrent refreshes in the same millisecond (sessions created at once)', async () => {
|
||||
const scriptPath = (await resolveStatusLineCliCommand(testDir, true))!;
|
||||
writeFileSync(scriptPath, '#!/bin/sh\n# CODEMAN_STATUSLINE_EXPORTER_V0\necho stale\n');
|
||||
// A frozen clock makes every writer agree on the timestamp, which is what two
|
||||
// session creates in one millisecond do. A temp name built from pid + Date.now()
|
||||
// is then shared: the first rename consumes it, every later rename fails ENOENT,
|
||||
// and that session's tmux create fell back to a direct PTY.
|
||||
const clock = vi.spyOn(Date, 'now').mockReturnValue(1_700_000_000_000);
|
||||
try {
|
||||
const paths = await Promise.all(Array.from({ length: 8 }, () => ensureStatusLineExporterScript()));
|
||||
expect(new Set(paths)).toEqual(new Set([scriptPath]));
|
||||
} finally {
|
||||
clock.mockRestore();
|
||||
}
|
||||
expect(readFileSync(scriptPath, 'utf-8')).not.toContain('echo stale');
|
||||
expect(statSync(scriptPath).mode & 0o111).not.toBe(0);
|
||||
const siblings = readdirSync(join(scriptPath, '..')).filter((f) => f.startsWith('statusline-exporter.sh.'));
|
||||
expect(siblings).toEqual([]);
|
||||
});
|
||||
|
||||
it('never overrides a real, hand-authored statusLine', async () => {
|
||||
const claudeDir = join(testDir, '.claude');
|
||||
mkdirSync(claudeDir, { recursive: true });
|
||||
|
||||
Reference in New Issue
Block a user