mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 10:39:32 +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
@@ -10879,6 +10879,9 @@ class CodemanApp {
|
||||
// An SSE resync (handleInit) that landed mid-request rebuilds the list from
|
||||
// the server, which still had the session then.
|
||||
if (this.sessions.has(sessionId)) this._onSessionDeleted({ id: sessionId });
|
||||
// The home screen read the outside-sessions list when the tab went, before this
|
||||
// delete landed, so its row would say "Go to tab" with no tab until the next poll.
|
||||
if (session?.adopt) void this.loadForeignSessions?.();
|
||||
if (killMux) {
|
||||
this.showToast('Session closed and tmux killed', 'success');
|
||||
} else {
|
||||
|
||||
@@ -37,6 +37,15 @@ import type { SessionPort, EventPort, RespawnPort, ConfigPort, InfraPort } from
|
||||
/** Cap on a remote `@fix_plan.md` read over ssh (a task list, not a document). */
|
||||
const MAX_REMOTE_FIX_PLAN_BYTES = 1024 * 1024;
|
||||
|
||||
/**
|
||||
* An adopted session's `workingDir` is the neutral wrapper folder `/tmp`, not a workspace.
|
||||
* Using it would write a predictable name into a folder anyone can write to, and read one
|
||||
* back, while the agent in the pane never sees either file.
|
||||
*/
|
||||
function refuseAdoptedRalphFile(file: string) {
|
||||
return createErrorResponse(ApiErrorCode.INVALID_INPUT, `${file} is not available for an adopted session`);
|
||||
}
|
||||
|
||||
export function registerRalphRoutes(
|
||||
app: FastifyInstance,
|
||||
ctx: SessionPort & EventPort & RespawnPort & ConfigPort & InfraPort
|
||||
@@ -208,6 +217,7 @@ export function registerRalphRoutes(
|
||||
app.post('/api/sessions/:id/fix-plan/write', async (req) => {
|
||||
const { id } = req.params as { id: string };
|
||||
const session = findSessionOrFail(ctx, id, req);
|
||||
if (session.isAdopted) return refuseAdoptedRalphFile('@fix_plan.md');
|
||||
// A remote session's workingDir is a path on the remote host. Writing it with local fs would land
|
||||
// the file in a same-named local folder (or fail), and remote writes are out of scope by design
|
||||
// (src/remote-files.ts is read-only), so it is refused, like the File Viewer's remote edit.
|
||||
@@ -244,6 +254,7 @@ export function registerRalphRoutes(
|
||||
app.post('/api/sessions/:id/fix-plan/read', async (req) => {
|
||||
const { id } = req.params as { id: string };
|
||||
const session = findSessionOrFail(ctx, id, req);
|
||||
if (session.isAdopted) return refuseAdoptedRalphFile('@fix_plan.md');
|
||||
|
||||
const workingDir = session.workingDir;
|
||||
if (!workingDir) {
|
||||
@@ -291,6 +302,7 @@ export function registerRalphRoutes(
|
||||
const { id } = req.params as { id: string };
|
||||
const { content } = parseBody(RalphPromptWriteSchema, req.body, 'Invalid request body');
|
||||
const session = findSessionOrFail(ctx, id, req);
|
||||
if (session.isAdopted) return refuseAdoptedRalphFile('@ralph_prompt.md');
|
||||
// Same rule as fix-plan/write: never write a remote session's file on the local disk.
|
||||
if (session.remote) {
|
||||
return createErrorResponse(
|
||||
|
||||
Reference in new issue
Block a user