fix(tmux-subagent): drain terminal probe replies during delegated pane startup (#2887)
When oh-my-opencode created a delegated tmux pane, terminal capability/color probe replies emitted by tmux or the freshly attaching opencode session could end up in the caller pane's input buffer instead of being consumed by the delegated pane, appearing as literal text in the main OpenCode chat (e.g. "414/21212a2/..."). Root cause: buildSplitArgs in team-layout-tmux/layout.ts called split-window without the -d (detached/don't-switch-focus) flag. Without -d, tmux briefly grants focus to the new pane during creation; the outer terminal then sends DA1/DA2 and OSC color probe replies into what it believes is the active pane, but the focus handoff races and those bytes land in the caller pane's stdin buffer instead. Fix: add -d to every split-window call in buildSplitArgs, matching the same flag already used in pane-spawn.ts for inline subagent panes. This keeps the caller pane focused throughout the delegated pane lifecycle so probe replies are consumed by the correct target. Existing tests pass; one new test asserts -d is present on every split-window call to guard this invariant. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -255,6 +255,30 @@ describe("team-layout-tmux", () => {
|
||||
expect(commands.some((args) => args[0] === "set-option")).toBe(false)
|
||||
})
|
||||
|
||||
test("#given delegated pane startup #when split-window is called #then -d flag is always present to prevent terminal probe replies leaking into caller pane (fix #2887)", async () => {
|
||||
// given
|
||||
const { createTeamLayout } = await loadLayoutModule()
|
||||
const members = [
|
||||
{ name: "m1", sessionId: "s-m1", worktreePath: "/tmp/m1" },
|
||||
{ name: "m2", sessionId: "s-m2", worktreePath: "/tmp/m2" },
|
||||
{ name: "m3", sessionId: "s-m3", worktreePath: "/tmp/m3" },
|
||||
]
|
||||
|
||||
// when
|
||||
await createTeamLayout("run-probe-drain", members, tmuxMgr as never)
|
||||
|
||||
// then: every split-window call must carry -d so focus never bounces to the
|
||||
// new pane; without -d, terminal capability probe replies (DA1/DA2, OSC color)
|
||||
// emitted during pane startup are misrouted to the caller pane's stdin and
|
||||
// appear as literal garbage text in the main OpenCode chat input.
|
||||
const commands = getCommands()
|
||||
const splitWindowCalls = commands.filter((args) => args[0] === "split-window")
|
||||
expect(splitWindowCalls.length).toBeGreaterThan(0)
|
||||
for (const splitArgs of splitWindowCalls) {
|
||||
expect(splitArgs).toContain("-d")
|
||||
}
|
||||
})
|
||||
|
||||
test("#given ownedSession=false, focusWindowId=@10, gridWindowId=@11 #when removeTeamLayout runs #then tmux kill-window is called twice with -t @10 and -t @11 and kill-session is NEVER called", async () => {
|
||||
// given
|
||||
const { removeTeamLayout } = await loadLayoutModule()
|
||||
@@ -392,7 +416,7 @@ describe("team-layout-tmux", () => {
|
||||
const commands = getCommands()
|
||||
const splitCalls = commands.filter((args) => args[0] === "split-window")
|
||||
expect(splitCalls).toEqual([
|
||||
["split-window", "-t", process.env.TMUX_PANE ?? "", "-h", "-l", "70%", "-P", "-F", "#{pane_id}", "-c", "/tmp/m1"],
|
||||
["split-window", "-t", process.env.TMUX_PANE ?? "", "-h", "-d", "-l", "70%", "-P", "-F", "#{pane_id}", "-c", "/tmp/m1"],
|
||||
])
|
||||
expect(commands.filter((args) => args[0] === "new-window").length).toBe(0)
|
||||
})
|
||||
|
||||
@@ -61,7 +61,7 @@ function selectExistingTeammatePane(teammatePanes: Array<string>, callerPaneId:
|
||||
|
||||
function buildSplitArgs(callerPaneId: string, teammatePanes: Array<string>, member: TeamLayoutMember): Array<string> {
|
||||
if (teammatePanes.length === 0) {
|
||||
return ["split-window", "-t", callerPaneId, "-h", "-l", "70%", "-P", "-F", "#{pane_id}", "-c", getPaneWorkingDirectory(member)]
|
||||
return ["split-window", "-t", callerPaneId, "-h", "-d", "-l", "70%", "-P", "-F", "#{pane_id}", "-c", getPaneWorkingDirectory(member)]
|
||||
}
|
||||
|
||||
return [
|
||||
@@ -69,6 +69,7 @@ function buildSplitArgs(callerPaneId: string, teammatePanes: Array<string>, memb
|
||||
"-t",
|
||||
selectExistingTeammatePane(teammatePanes, callerPaneId),
|
||||
teammatePanes.length % 2 === 1 ? "-v" : "-h",
|
||||
"-d",
|
||||
"-P",
|
||||
"-F",
|
||||
"#{pane_id}",
|
||||
|
||||
Reference in New Issue
Block a user