What's broken
The create_workspace MCP tool accepts isolation: "local" for paths that do not name a real directory. Both a nonexistent path and a regular file return isError: false, allocate project/workspace IDs, and describe the result as kind: "directory".
I expected local isolation to adopt an existing directory only. A missing path should return an actionable “no such directory” error, and a regular file should return “not a directory,” without persisting a successful-looking workspace. The current result defers the failure until an agent or another filesystem operation tries to use the invalid cwd.
I searched open and closed issues and PRs before filing, including create_workspace + directory/path, isolation: local, create workspace + nonexistent, local workspace + not a directory, workspace record + does not exist, and PR number 2884. No issue reported this behavior. #3756 concerns project leakage from disposable local-isolation automation and is not the same validation failure; the only exact match was the unlinked candidate-fix PR.
Steps to reproduce
Tested from a clean checkout of current getpaseo/paseo main:
commit 92504cd525c7e594539a369ed441eb836372dd2d
package version 0.8.0-beta.1
Node v24.19.0
npm 11.17.0
I used a focused failing-first E2E test. It starts a real in-process daemon on 127.0.0.1:0, connects through the public /mcp/agents Streamable HTTP endpoint, and calls the real tool twice:
const root = await mkdtemp(path.join(os.tmpdir(), "paseo-pr2884-main-"));
const paseoHome = path.join(root, "state");
const missingPath = path.join(root, "does-not-exist");
const filePath = path.join(root, "regular-file");
await writeFile(filePath, "not a directory\n");
const daemon = await createPaseoDaemon(
{
listen: "127.0.0.1:0",
paseoHome,
corsAllowedOrigins: [],
hostnames: true,
mcpEnabled: true,
staticDir: path.join(root, "static"),
mcpDebug: false,
agentClients: createTestAgentClients(),
agentStoragePath: path.join(paseoHome, "agents"),
},
pino({ level: "silent" }),
);
await daemon.start();
const target = daemon.getListenTarget();
const client = await experimental_createMCPClient({
transport: new StreamableHTTPClientTransport(
new URL(`http://127.0.0.1:${target.port}/mcp/agents`),
),
});
const missing = await client.callTool({
name: "create_workspace",
args: { isolation: "local", path: missingPath },
});
const regularFile = await client.callTool({
name: "create_workspace",
args: { isolation: "local", path: filePath },
});
expect({
missingIsError: missing.isError ?? false,
fileIsError: regularFile.isError ?? false,
}).toEqual({ missingIsError: true, fileIsError: true });
Command:
npx vitest run packages/server/src/server/agent/pr2884-repro.e2e.test.ts --bail=1
Exact tool results and assertion failure:
{
"missingPathExists": false,
"missingResult": {
"content": [{
"type": "text",
"text": "{\n \"workspaceId\": \"wks_77d339c6f39ea579\",\n \"projectId\": \"prj_7c8977ac9614d73e\",\n \"cwd\": \"/tmp/paseo-pr2884-main-nTF4dQ/does-not-exist\",\n \"isolation\": \"local\",\n \"kind\": \"directory\",\n \"title\": null\n}"
}],
"isError": false,
"structuredContent": {
"workspaceId": "wks_77d339c6f39ea579",
"projectId": "prj_7c8977ac9614d73e",
"cwd": "/tmp/paseo-pr2884-main-nTF4dQ/does-not-exist",
"isolation": "local",
"kind": "directory",
"title": null
}
},
"filePathIsDirectory": false,
"fileResult": {
"content": [{
"type": "text",
"text": "{\n \"workspaceId\": \"wks_bee93beff2a9ee21\",\n \"projectId\": \"prj_b5f25eafbf40c815\",\n \"cwd\": \"/tmp/paseo-pr2884-main-nTF4dQ/regular-file\",\n \"isolation\": \"local\",\n \"kind\": \"directory\",\n \"title\": null\n}"
}],
"isError": false,
"structuredContent": {
"workspaceId": "wks_bee93beff2a9ee21",
"projectId": "prj_b5f25eafbf40c815",
"cwd": "/tmp/paseo-pr2884-main-nTF4dQ/regular-file",
"isolation": "local",
"kind": "directory",
"title": null
}
}
}
AssertionError: expected { missingIsError: false, …(1) } to deeply equal { missingIsError: true, …(1) }
- Expected
+ Received
{
- "fileIsError": true,
- "missingIsError": true,
+ "fileIsError": false,
+ "missingIsError": false,
}
Test Files 1 failed (1)
Tests 1 failed (1)
The test finally block closed the MCP client, stopped the daemon, and recursively removed its unique temporary root. The temporary reproduction test file was then deleted. Port 6767 and ~/.paseo were not used.
Where did this happen
Daemon
Paseo version
Current main 92504cd525c7e594539a369ed441eb836372dd2d (package version 0.8.0-beta.1)
OS version
NixOS 26.11 (Zokor), Linux 7.2.3 x86_64
Agent provider
Not relevant
Provider configuration
Not relevant; the MCP calls use the daemon's offline fake agent clients but do not create an agent.
Logs
The exact MCP responses and failing assertion are included above. The daemon did not log an error because both calls were treated as successful.
Screenshots or video
Not applicable (MCP API behavior).
Candidate fix
#2884 is an existing candidate fix. It was not applied during this reproduction, and this report does not assume its diagnosis is correct until reviewed.
What's broken
The
create_workspaceMCP tool acceptsisolation: "local"for paths that do not name a real directory. Both a nonexistent path and a regular file returnisError: false, allocate project/workspace IDs, and describe the result askind: "directory".I expected local isolation to adopt an existing directory only. A missing path should return an actionable “no such directory” error, and a regular file should return “not a directory,” without persisting a successful-looking workspace. The current result defers the failure until an agent or another filesystem operation tries to use the invalid
cwd.I searched open and closed issues and PRs before filing, including
create_workspace+ directory/path,isolation: local,create workspace+ nonexistent,local workspace+not a directory,workspace record+does not exist, and PR number2884. No issue reported this behavior. #3756 concerns project leakage from disposable local-isolation automation and is not the same validation failure; the only exact match was the unlinked candidate-fix PR.Steps to reproduce
Tested from a clean checkout of current
getpaseo/paseomain:I used a focused failing-first E2E test. It starts a real in-process daemon on
127.0.0.1:0, connects through the public/mcp/agentsStreamable HTTP endpoint, and calls the real tool twice:Command:
Exact tool results and assertion failure:
The test
finallyblock closed the MCP client, stopped the daemon, and recursively removed its unique temporary root. The temporary reproduction test file was then deleted. Port 6767 and~/.paseowere not used.Where did this happen
Daemon
Paseo version
Current main
92504cd525c7e594539a369ed441eb836372dd2d(package version0.8.0-beta.1)OS version
NixOS 26.11 (Zokor), Linux 7.2.3 x86_64
Agent provider
Not relevant
Provider configuration
Not relevant; the MCP calls use the daemon's offline fake agent clients but do not create an agent.
Logs
The exact MCP responses and failing assertion are included above. The daemon did not log an error because both calls were treated as successful.
Screenshots or video
Not applicable (MCP API behavior).
Candidate fix
#2884 is an existing candidate fix. It was not applied during this reproduction, and this report does not assume its diagnosis is correct until reviewed.