mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-15 18:59:33 +01:00
fix: adopted tmux targets, hung servers and the attach chain (release review)
Names tmux reads as target syntax (`@1`, `%1`, `=eq`, a bare `~`) passed the has-session guard by name and then joined something else in new-session -t: another session, or a fresh shell on the foreign server. They are refused at discovery now, and take the existing skipped note. One stopped tmux server emptied the whole local list and left a stuck client per scan. Each server now gets a bounded ping with stdout at /dev/null first, because a tmux client hands dups of its stdout to the server: a stopped server holds our pipe open after the client is dead, so a watchdog around list-panes cannot work. The probe runs in its own process group, all three transports keep the rows read before a failure, and a missed ping becomes a note. The attach and the probe pass -u, so a POSIX-locale container no longer garbles the tab or lists a non-ASCII name as underscores. A docker attach gets the same TERM and COLORTERM an owned container's exec gets. A missing tmux no longer reads as "session is gone", and the launch line's `$` now reaches bash unexpanded, so the ssh attach's PATH line is expanded at the far end, as the probe's already was. fix-plan/write, fix-plan/read and ralph-prompt/write refuse an adopted session: its workingDir is the neutral /tmp, so they read and wrote a predictable name in a folder anyone can write to. FOREIGN_MAX_SOCKETS is gone: it capped nothing, and a real cap in glob order would never reach `default` on a box with many `codeman-*` sockets (this one has 212). Closing an adopted tab re-reads the outside-sessions list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
36d8942744
commit
aa40cca343
9 files changed
+620
-81
No files matched your search
@@ -14,7 +14,8 @@
|
||||
*
|
||||
* Port: none (app.inject()).
|
||||
*/
|
||||
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
|
||||
@@ -102,6 +103,43 @@ describe('adopted sessions refuse every auto-op that drives the pane', () => {
|
||||
expect(res.json().error).toMatch(/adopted/i);
|
||||
await h.app.close();
|
||||
});
|
||||
|
||||
// ⚠️ Neither of these is a mock-only concern: an adopted session's `workingDir` is the
|
||||
// neutral wrapper folder `/tmp`, so a live one wrote `/tmp/@ralph_prompt.md` on the host
|
||||
// and answered success, a predictable name in a folder anyone can write to.
|
||||
describe.each([
|
||||
['fix-plan/write', {}],
|
||||
['fix-plan/read', {}],
|
||||
['ralph-prompt/write', { content: 'prompt' }],
|
||||
])('POST %s', (route, payload) => {
|
||||
let dir: string;
|
||||
beforeEach(() => {
|
||||
dir = mkdtempSync(join(tmpdir(), 'adopted-ralph-'));
|
||||
writeFileSync(join(dir, '@fix_plan.md'), '- [ ] task\n');
|
||||
});
|
||||
afterEach(() => rmSync(dir, { recursive: true, force: true }));
|
||||
|
||||
const post = async (isAdopted: boolean) => {
|
||||
const h = await createRouteTestHarness(registerRalphRoutes, { sessionId: SID });
|
||||
const ralphTracker = { todos: [], generateFixPlanMarkdown: () => '', importFixPlanMarkdown: () => 0 };
|
||||
Object.assign(h.ctx._session as object, { isAdopted, workingDir: dir, ralphTracker });
|
||||
const res = await h.app.inject({ method: 'POST', url: `/api/sessions/${SID}/${route}`, payload });
|
||||
await h.app.close();
|
||||
return res.json();
|
||||
};
|
||||
|
||||
it('refuses an adopted session and touches nothing', async () => {
|
||||
const before = readdirSync(dir);
|
||||
const body = await post(true);
|
||||
expect(body.success).toBe(false);
|
||||
expect(body.error).toMatch(/adopted/i);
|
||||
expect(readdirSync(dir)).toEqual(before);
|
||||
});
|
||||
|
||||
it('still answers for a session Codeman started', async () => {
|
||||
expect((await post(false)).success).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /api/mux/foreign', () => {
|
||||
|
||||
Reference in new issue
Block a user