diff --git a/extensions/agent-chain.ts b/extensions/agent-chain.ts index 8cf7d2a..e50776c 100644 --- a/extensions/agent-chain.ts +++ b/extensions/agent-chain.ts @@ -25,6 +25,7 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; import { Type } from "@sinclair/typebox"; import { Text, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui"; import { spawn } from "child_process"; +import { realpathSync } from "fs"; import { readFileSync, existsSync, readdirSync, mkdirSync, unlinkSync } from "fs"; import { join, resolve } from "path"; import { applyExtensionDefaults } from "./themeMap.ts"; @@ -364,7 +365,10 @@ export default function (pi: ExtensionAPI) { const state = stepStates[stepIndex]; return new Promise((resolve) => { - const proc = spawn("pi", args, { + // Use process.execPath (real node) + pi script to bypass snap AppArmor confinement. + const nodeBin = process.execPath; + const piScript = (() => { try { return realpathSync(process.argv[1]); } catch { return process.argv[1]; } })(); + const proc = spawn(nodeBin, [piScript, ...args], { stdio: ["ignore", "pipe", "pipe"], env: { ...process.env }, }); diff --git a/extensions/agent-team.ts b/extensions/agent-team.ts index 66ecbef..87a4c3e 100644 --- a/extensions/agent-team.ts +++ b/extensions/agent-team.ts @@ -21,6 +21,7 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; import { Type } from "@sinclair/typebox"; import { Text, type AutocompleteItem, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui"; import { spawn } from "child_process"; +import { realpathSync } from "fs"; import { readdirSync, readFileSync, existsSync, mkdirSync, unlinkSync } from "fs"; import { join, resolve } from "path"; import { applyExtensionDefaults } from "./themeMap.ts"; @@ -365,7 +366,11 @@ export default function (pi: ExtensionAPI) { const textChunks: string[] = []; return new Promise((resolve) => { - const proc = spawn("pi", args, { + // Use process.execPath (real node binary) + resolved pi script to bypass + // snap AppArmor confinement that breaks pipes on snap-to-snap spawns. + const nodeBin = process.execPath; + const piScript = (() => { try { return realpathSync(process.argv[1]); } catch { return process.argv[1]; } })(); + const proc = spawn(nodeBin, [piScript, ...args], { stdio: ["ignore", "pipe", "pipe"], env: { ...process.env }, }); diff --git a/extensions/pi-pi.ts b/extensions/pi-pi.ts index 97c46d2..eb38816 100644 --- a/extensions/pi-pi.ts +++ b/extensions/pi-pi.ts @@ -19,6 +19,7 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; import { Type } from "@sinclair/typebox"; import { Text, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui"; import { spawn } from "child_process"; +import { realpathSync } from "fs"; import { readdirSync, readFileSync, existsSync, mkdirSync } from "fs"; import { join, resolve } from "path"; import { applyExtensionDefaults } from "./themeMap.ts"; @@ -291,7 +292,10 @@ export default function (pi: ExtensionAPI) { const textChunks: string[] = []; return new Promise((resolve) => { - const proc = spawn("pi", args, { + // Use process.execPath (real node) + pi script to bypass snap AppArmor confinement. + const nodeBin = process.execPath; + const piScript = (() => { try { return realpathSync(process.argv[1]); } catch { return process.argv[1]; } })(); + const proc = spawn(nodeBin, [piScript, ...args], { stdio: ["ignore", "pipe", "pipe"], env: { ...process.env }, }); diff --git a/extensions/subagent-widget.ts b/extensions/subagent-widget.ts index a31ac6e..89489db 100644 --- a/extensions/subagent-widget.ts +++ b/extensions/subagent-widget.ts @@ -16,7 +16,8 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; import { DynamicBorder } from "@mariozechner/pi-coding-agent"; import { Container, Text } from "@mariozechner/pi-tui"; import { Type } from "@sinclair/typebox"; -const { spawn } = require("child_process") as any; +import { spawn } from "child_process"; +import { realpathSync } from "fs"; import * as fs from "fs"; import * as os from "os"; import * as path from "path"; @@ -139,7 +140,12 @@ export default function (pi: ExtensionAPI) { : "openrouter/google/gemini-3-flash-preview"; return new Promise((resolve) => { - const proc = spawn("pi", [ + // Use process.execPath (real node binary, not snap wrapper) + resolved pi script + // to bypass snap AppArmor confinement that breaks pipes on snap-to-snap spawns. + const nodeBin = process.execPath; + const piScript = (() => { try { return realpathSync(process.argv[1]); } catch { return process.argv[1]; } })(); + const proc = spawn(nodeBin, [ + piScript, "--mode", "json", "-p", "--session", state.sessionFile, // persistent session for /subcont resumption