feat(engine): allow read of sibling task PROMPT.md/task.json for deps
Agents working on a task that depends on other tasks (e.g. documentation alignment tasks needing the sibling tasks' specs) were repeatedly rejected by the worktree boundary when reading .fusion/tasks/FN-NNNN/PROMPT.md, which also contributed to the malformed-tool-result crash we just fixed. Add a read-only exception to isWorktreeAllowedPath: the read/glob/grep tools may access .fusion/tasks/*/PROMPT.md and .fusion/tasks/*/task.json at the project root. Writes and bash cwd remain restricted. Update the system-prompt boundary docs (executor.ts) so agents know the exception exists and stop burning turns re-trying rejected reads. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -331,6 +331,7 @@ You are running in an **isolated git worktree**. This means:
|
||||
- **All code changes must be made inside the current worktree directory.** Do not modify files outside the worktree — the worktree is your isolated execution environment.
|
||||
- **Exception — Project memory:** You MAY read and write to files under .fusion/memory/ at the project root to save durable project learnings (architecture patterns, conventions, pitfalls).
|
||||
- **Exception — Task attachments:** You MAY read files under .fusion/tasks/{taskId}/attachments/ at the project root for context screenshots and documents attached to this task.
|
||||
- **Exception — Sibling task specs:** You MAY read .fusion/tasks/{taskId}/PROMPT.md and .fusion/tasks/{taskId}/task.json at the project root (read-only) to consult dependency tasks' specifications.
|
||||
- **Shell commands** run inside the worktree by default. Avoid using cd to navigate outside the worktree.
|
||||
|
||||
If you attempt to write to a path outside the worktree, the file tools will reject the operation with an error explaining the boundary.
|
||||
@@ -4929,6 +4930,7 @@ You are running in an **isolated git worktree**. This means:
|
||||
- **All code changes must be made inside the current worktree directory.** Do not modify files outside the worktree.
|
||||
- **Exception — Project memory:** You MAY read and write to files under \`.fusion/memory/\` at the project root to save durable project learnings.
|
||||
- **Exception — Task attachments:** You MAY read files under \`.fusion/tasks/{taskId}/attachments/\` at the project root for context.
|
||||
- **Exception — Sibling task specs:** You MAY read \`.fusion/tasks/{taskId}/PROMPT.md\` and \`.fusion/tasks/{taskId}/task.json\` at the project root (read-only) to consult dependency tasks' specifications.
|
||||
- **Shell commands** run inside the worktree by default. Avoid using \`cd\` to navigate outside the worktree.
|
||||
|
||||
## Begin
|
||||
|
||||
@@ -781,14 +781,22 @@ async function assertValidWorktreeSession(cwd: string, projectRoot: string): Pro
|
||||
* - Paths inside the worktree are always allowed
|
||||
* - Project root .fusion/memory/ files are allowed (for durable project learnings)
|
||||
* - Task attachments under .fusion/tasks/N/attachments/ are allowed (for reading context files)
|
||||
* - Sibling task specs (.fusion/tasks/N/PROMPT.md and task.json) are allowed for
|
||||
* read-only tools (read/glob/grep) so agents can consult dependency specs.
|
||||
* - All other paths outside the worktree are rejected
|
||||
*
|
||||
* @param worktreePath - Absolute path to the worktree directory
|
||||
* @param projectRoot - Absolute path to the project root (derived from worktree)
|
||||
* @param requestedPath - The path being accessed
|
||||
* @param toolName - Tool making the request (controls read-only exceptions)
|
||||
* @returns true if allowed, false if rejected
|
||||
*/
|
||||
function isWorktreeAllowedPath(worktreePath: string, projectRoot: string, requestedPath: string): boolean {
|
||||
function isWorktreeAllowedPath(
|
||||
worktreePath: string,
|
||||
projectRoot: string,
|
||||
requestedPath: string,
|
||||
toolName?: string,
|
||||
): boolean {
|
||||
// Normalize paths
|
||||
const worktreeResolved = resolve(worktreePath);
|
||||
const projectRootResolved = resolve(projectRoot);
|
||||
@@ -815,6 +823,16 @@ function isWorktreeAllowedPath(worktreePath: string, projectRoot: string, reques
|
||||
return true;
|
||||
}
|
||||
|
||||
// Exception (read-only): sibling task specs so the agent can consult the
|
||||
// PROMPT.md / task.json of dependency tasks without needing them copied
|
||||
// into the worktree. `glob`/`grep` are narrow enough to allow as well so
|
||||
// the agent can discover them; writes and bash remain restricted.
|
||||
const readOnlyTools = new Set(["read", "glob", "grep"]);
|
||||
if (toolName && readOnlyTools.has(toolName) &&
|
||||
/^\.fusion\/tasks\/[^/]+\/(PROMPT\.md|task\.json)$/.test(relToProjectRoot)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// All other paths outside the worktree are rejected
|
||||
return false;
|
||||
}
|
||||
@@ -874,18 +892,19 @@ export function wrapToolsWithBoundary(
|
||||
|
||||
// Check path argument for file operations
|
||||
const pathArg = params.path as string | undefined;
|
||||
if (pathArg && !isWorktreeAllowedPath(worktreePath, projectRoot, pathArg)) {
|
||||
if (pathArg && !isWorktreeAllowedPath(worktreePath, projectRoot, pathArg, tool.name)) {
|
||||
const relToProject = relative(projectRoot, pathArg);
|
||||
return boundaryRejection(
|
||||
`Path "${relToProject}" is outside the worktree boundary. ` +
|
||||
`Coding agents can only modify files inside the current worktree. ` +
|
||||
`Exception: .fusion/memory/ (project root) and .fusion/tasks/*/attachments/* are permitted for reading.`,
|
||||
`Exceptions (read-only): .fusion/memory/, .fusion/tasks/*/attachments/, ` +
|
||||
`and .fusion/tasks/*/{PROMPT.md,task.json} for dependency context.`,
|
||||
);
|
||||
}
|
||||
|
||||
// For bash, also check the working directory if specified
|
||||
const cwdArg = params.cwd as string | undefined;
|
||||
if (tool.name === "bash" && cwdArg && !isWorktreeAllowedPath(worktreePath, projectRoot, cwdArg)) {
|
||||
if (tool.name === "bash" && cwdArg && !isWorktreeAllowedPath(worktreePath, projectRoot, cwdArg, tool.name)) {
|
||||
return boundaryRejection(
|
||||
`Working directory is outside the worktree boundary. ` +
|
||||
`Commands must run inside the worktree.`,
|
||||
|
||||
Reference in New Issue
Block a user