mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 18:39:33 +01:00
fix(mux): treat the launch directory as no answer for a discovered pane
Every pane starts in TMUX_LAUNCH_CWD (/tmp) and reaches its workspace with a cd inside the pane command, but a remote or docker pane never does that cd, so tmux reports /tmp for it. A rediscovered record carries no remote/docker metadata and looks local, so adopting that directory would root the session at /tmp: hooks written into /tmp/.claude, the Files panel at /tmp, and a respawn running claude in a directory any local account can write to. reconcileSessions() now skips a pane whose directory is the launch directory, in both spellings a pane can report it: the literal one and its realpath (/private/tmp on macOS, where a process cwd comes back resolved). The set is computed once at module load. Such a session falls back to process.cwd(), as before this series. Also stop trimming the list-panes output: the directory is the last field, so a trim cut a trailing space off the last row's path; parsePaneRows() already skips empty lines. And the comments that list what a discovered record knows for certain now include the directory, when tmux reported a usable one. Co-Authored-By: Christian Haberl <116439789+christianhaberl@users.noreply.github.com> Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
3bd7e81233
commit
ced88b3310
3 files changed
+82
-6
No files matched your search
@@ -52,11 +52,24 @@ vi.mock('node:child_process', async () => {
|
||||
};
|
||||
});
|
||||
|
||||
// Paths realpathSync should resolve differently than this host does, e.g. a
|
||||
// macOS `/tmp` -> `/private/tmp`. Hoisted so it survives vi.resetModules().
|
||||
const realpathOverrides = vi.hoisted(() => new Map<string, string>());
|
||||
|
||||
// Mock fs to avoid file I/O
|
||||
vi.mock('node:fs', async () => {
|
||||
const actual = await vi.importActual('node:fs');
|
||||
const actual = await vi.importActual<typeof import('node:fs')>('node:fs');
|
||||
const realpathSync = Object.assign(
|
||||
vi.fn((path: string, ...rest: unknown[]) =>
|
||||
realpathOverrides.has(String(path))
|
||||
? realpathOverrides.get(String(path))
|
||||
: (actual.realpathSync as (...args: unknown[]) => string)(path, ...rest)
|
||||
),
|
||||
{ native: actual.realpathSync.native }
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
realpathSync,
|
||||
existsSync: vi.fn().mockReturnValue(false),
|
||||
readFileSync: vi.fn(),
|
||||
mkdirSync: vi.fn(),
|
||||
@@ -916,6 +929,10 @@ describe('TmuxManager (unit)', () => {
|
||||
'codeman-cccc3333|104|0|||/home/user/$(touch pwned)',
|
||||
// A tmux that reported no path.
|
||||
'codeman-dddd4444|105|0|||',
|
||||
// The launch directory every pane starts in; a remote or docker pane never leaves it.
|
||||
'codeman-eeee5555|106|0|||/tmp',
|
||||
// Last row, path ending in a space: the output must not be trimmed.
|
||||
'codeman-ffff6666|107|0|||/home/user/trailing ',
|
||||
].join('\n');
|
||||
}
|
||||
return '';
|
||||
@@ -930,6 +947,8 @@ describe('TmuxManager (unit)', () => {
|
||||
'restored-bbbb2222',
|
||||
'restored-cccc3333',
|
||||
'restored-dddd4444',
|
||||
'restored-eeee5555',
|
||||
'restored-ffff6666',
|
||||
]);
|
||||
const dirs = Object.fromEntries(nonTestManager.getSessions().map((s) => [s.muxName, s.workingDir]));
|
||||
expect(dirs).toEqual({
|
||||
@@ -937,6 +956,8 @@ describe('TmuxManager (unit)', () => {
|
||||
'codeman-bbbb2222': '/home/user/project-b',
|
||||
'codeman-cccc3333': process.cwd(),
|
||||
'codeman-dddd4444': process.cwd(),
|
||||
'codeman-eeee5555': process.cwd(),
|
||||
'codeman-ffff6666': '/home/user/trailing ',
|
||||
});
|
||||
|
||||
// The directory comes from the same list-panes call, as its last field.
|
||||
@@ -949,6 +970,33 @@ describe('TmuxManager (unit)', () => {
|
||||
nonTestManager.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
it('treats the resolved spelling of the launch directory as no answer too (macOS /private/tmp)', async () => {
|
||||
// On macOS /tmp is a symlink and a pane reports its process cwd resolved.
|
||||
realpathOverrides.set('/tmp', '/private/tmp');
|
||||
mockedExecSync.mockImplementation((cmd: string) => {
|
||||
if (typeof cmd === 'string' && cmd.includes(' list-panes ')) {
|
||||
return ['codeman-aaaa1111|101|0|||/private/tmp', 'codeman-bbbb2222|102|0|||/tmp'].join('\n');
|
||||
}
|
||||
return '';
|
||||
});
|
||||
try {
|
||||
const NonTestTmuxManager = await importWithTmuxCommandsEnabled();
|
||||
const nonTestManager = new NonTestTmuxManager();
|
||||
try {
|
||||
await nonTestManager.reconcileSessions();
|
||||
const dirs = Object.fromEntries(nonTestManager.getSessions().map((s) => [s.muxName, s.workingDir]));
|
||||
expect(dirs).toEqual({
|
||||
'codeman-aaaa1111': process.cwd(),
|
||||
'codeman-bbbb2222': process.cwd(),
|
||||
});
|
||||
} finally {
|
||||
nonTestManager.destroy();
|
||||
}
|
||||
} finally {
|
||||
realpathOverrides.clear();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user