fix(panes): /tui works in panes with workspace instructions

Claude refused `/tui` ("Cannot switch renderers in this session — it has
restrictions a restart can't carry over") in every pane started with
--append-system-prompt-file, i.e. any workspace with instructions (member
panes usually have the owner's). The instructions are now written as the only
CLAUDE.md of a folder of their own (<data dir>/workspace-prompts/<id>/, a
member's prompt-<id>/ in the handoff dir via write-as), passed as one more
--add-dir with CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1. Verified with
Claude Code 2.1.301: the file is listed in /memory and /tui switches. Codex
is unchanged (its pointer line names the same file).
This commit is contained in:
2026-10-16 13:38:09 +07:00
parent 5e0a9b526b
commit 7c5f30df7d
7 changed files with 45 additions and 25 deletions
+16 -9
View File
@@ -7,7 +7,7 @@
* workspace's own folder and not be or contain the OS home; an uploaded file
* is stored by AgentHub under `<data dir>/workspace-context/<laneId>/` with a
* name it chooses, so a request never names a path on disk. Only Claude
* (`--add-dir`, `--append-system-prompt-file=`) and Codex (`--add-dir`,
* (`--add-dir`, the instructions as a CLAUDE.md in a folder of their own) and Codex (`--add-dir`,
* `-c developer_instructions=`) can take them; every other tool is reported
* unsupported and its panes get nothing. The workspace OWNER's own agent
* instructions (`user_prefs.instructions`, Settings → My agent instructions)
@@ -184,7 +184,9 @@ function removeSource(laneId, sourceId) {
function removeLaneContext(laneId) {
const dir = contextDir(laneId);
if (ownsFile(dir)) fs.rmSync(dir, { recursive: true, force: true });
fs.rmSync(path.join(getDataDir(), "workspace-prompts", `${Number(laneId)}.md`), { force: true });
const prompts = path.join(getDataDir(), "workspace-prompts");
fs.rmSync(path.join(prompts, String(Number(laneId))), { recursive: true, force: true });
fs.rmSync(path.join(prompts, `${Number(laneId)}.md`), { force: true }); // before v2.8.9
}
/** Instructions text as stored: a string of at most MAX_INSTRUCTIONS, blank ⇒ null. */
@@ -286,18 +288,23 @@ function launchExtras(laneId, tool) {
// A data dir that cannot be written costs the pane its instructions (all of
// Claude's, Codex's past the inline budget), never its start — same as a
// vanished source.
// The file is a `CLAUDE.md` alone in a folder of its own: Claude reads it as
// memory from that folder (`--add-dir` + CLAUDE_CODE_ADDITIONAL_DIRECTORIES_
// CLAUDE_MD, pty-run.js), not as `--append-system-prompt-file` — a custom
// system prompt is a launch flag Claude cannot carry over a restart, so it
// refused `/tui` (switching renderer restarts the session) in every pane
// that had instructions. Codex names the same file in its pointer line.
try {
const sub = `prompt-${Number(laneId)}`;
if (member) {
const file = require("./run-as").handoffWrite(
member,
`prompt-${Number(laneId)}.md`,
systemPrompt
);
const runAs = require("./run-as");
runAs.mkdirAs(member, path.join(runAs.handoffDir(member), sub));
const file = runAs.handoffWrite(member, path.join(sub, "CLAUDE.md"), systemPrompt);
return { addDirs, systemPrompt, systemPromptFile: file };
}
const dir = path.join(getDataDir(), "workspace-prompts");
const dir = path.join(getDataDir(), "workspace-prompts", String(Number(laneId)));
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
const systemPromptFile = path.join(dir, `${Number(laneId)}.md`);
const systemPromptFile = path.join(dir, "CLAUDE.md");
fs.writeFileSync(systemPromptFile, systemPrompt, { mode: 0o600 });
return { addDirs, systemPrompt, systemPromptFile };
} catch (err) {