mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-05 23:19:43 +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
|
// 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.
|
// windows in which Claude Code could run an empty or non-executable file.
|
||||||
// rename() swaps the complete, already-executable file in atomically.
|
// 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 writeFile(tmpPath, desired);
|
||||||
await chmod(tmpPath, 0o755);
|
await chmod(tmpPath, 0o755);
|
||||||
await rename(tmpPath, scriptPath);
|
await rename(tmpPath, scriptPath);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
* hook definitions for desktop notifications.
|
* 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 {
|
import {
|
||||||
chmodSync,
|
chmodSync,
|
||||||
closeSync,
|
closeSync,
|
||||||
@@ -26,6 +26,7 @@ import { spawn } from 'node:child_process';
|
|||||||
import {
|
import {
|
||||||
applyStatusLineConfig,
|
applyStatusLineConfig,
|
||||||
ensureCodemanHooks,
|
ensureCodemanHooks,
|
||||||
|
ensureStatusLineExporterScript,
|
||||||
findEffectiveUserStatusLineCommand,
|
findEffectiveUserStatusLineCommand,
|
||||||
generateBackgroundWakeScript,
|
generateBackgroundWakeScript,
|
||||||
generateHooksConfig,
|
generateHooksConfig,
|
||||||
@@ -1418,6 +1419,26 @@ describe('resolveStatusLineCliCommand', () => {
|
|||||||
expect(siblings).toEqual([]);
|
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 () => {
|
it('never overrides a real, hand-authored statusLine', async () => {
|
||||||
const claudeDir = join(testDir, '.claude');
|
const claudeDir = join(testDir, '.claude');
|
||||||
mkdirSync(claudeDir, { recursive: true });
|
mkdirSync(claudeDir, { recursive: true });
|
||||||
|
|||||||
Reference in New Issue
Block a user