Embedded agent still sandboxed to a single MFE's projectRoot — cross-MFE agentRoot work is implemented but not wired/released
Summary
In a single-spa / micro-frontend setup where the root shell and every MFE each
run their own Vite dev server (annotask({ mfe, server }) pointed at the root
server, per the docs), a task whose target file lives in a sibling MFE
still fails with a permission/access error when the embedded agent tries to
read or edit it — even though:
- the consuming repo's MFE dev-server configs already POST their identity to
the root server on boot (registerWithRootServer → POST /__annotask/api/mfes/register) via the documented annotask({ mfe, server }) options, and
- a
pnpm-workspace.yaml was added at the monorepo root specifically to give
resolveWorkspace() a workspace root to discover.
Neither fix helps, because the embedded coding-agent CLI (claude/codex/
opencode/copilot) is still spawned with cwd: projectRoot — the single
MFE's own directory — not the broader agentRoot the registry is designed to
compute. The spawned CLI enforces its own filesystem sandbox relative to that
cwd, so bash/glob/view calls against ../mfes/<sibling>/... are refused
by the CLI itself, independent of anything Annotask's own HTTP API permits.
- Affects:
annotask@0.4.5 (latest published; confirmed via installed
node_modules/annotask in a consumer repo — zero references to
mfe-registry, computeAgentRoot, mfes/register, or agentRoot
anywhere in dist/)
- Area (this repo, local
main checkout): src/server/mfe-registry.ts,
src/server/agent-spawn.ts, src/server/api.ts
Steps to reproduce (consumer repo)
- A monorepo-style layout:
apps/root (shell, runs the Annotask server) +
apps/mfes/<name> (several sibling MFEs), each MFE's vite.config.ts
passing annotask({ mfe: '@org/<name>', server: 'http://localhost:5174' })
pointed at the root's dev server port.
- Run the root shell + at least one MFE locally (e.g. via a script that
cds into each package and runs vite).
- Open the Annotask UI at the root's origin, select an element that's
actually rendered by the sibling MFE (not the shell), and ask the embedded
agent to make an edit.
- The agent locates the file's path correctly (it's stamped in the task's
anchor / grounding data) but reports something like: "Access to
../mfes/<name>/ is blocked in this sandbox — my tools only permit
reads/writes within <root's own projectRoot> and its children... every
attempt returns 'Permission denied'."
Expected
Per this repo's own src/server/mfe-registry.ts doc comment:
From the union of the root projectRoot + every registered MFE root we
derive the agent root — their nearest common ancestor — which becomes:
- the embedded agent's spawn cwd (so it can read/write every MFE), and
- the path-containment root for cross-MFE file resolution
i.e. the spawned CLI's cwd should be the computed agentRoot, not the
single MFE's projectRoot, whenever one or more MFEs have registered.
Actual
src/server/agent-spawn.ts (handleSpawn) still spawns with a hardcoded
cwd: projectRoot:
child = (opts.spawnImpl ?? spawn)(parsed.cli, parsed.args, {
cwd: projectRoot,
shell: false,
...
})
— this is the published 0.4.5 behavior. On the local main checkout,
this has actually already been fixed: src/server/api.ts now resolves
const agentRoot = await getAgentRoot(options.projectRoot) and calls
options.agentSpawn.handleSpawn(req, res, parsed.data, agentRoot) (api.ts
~L1179-1180), so the spawn cwd is the computed agent root there.
- However,
src/server/mfe-registry.ts and its test file are untracked
(git status shows ??) — this work has not been committed, let alone
released. It is not in CHANGELOG.md (latest entry is 0.4.4, which
predates this file), and the installed 0.4.5 tarball has none of it.
- So today, every consumer on the published package hits the sandbox error
above with no workaround — pnpm-workspace.yaml / resolveWorkspace()
only ever fed the component-scanning / path-containment side (already
present since 0.4.2's "Cross-MFE apply and byte-exact undo"), never the
actual agent spawn cwd.
Ask
- Commit
src/server/mfe-registry.ts + its test, and confirm api.ts's
existing getAgentRoot(options.projectRoot) → handleSpawn(..., agentRoot)
wiring is complete (it looks done from a read-through, but hasn't been
exercised against a real multi-MFE consumer since it's uncommitted).
- Add a CHANGELOG entry and cut a release once verified — consumers can't
get this fix any other way; there's no client-side or config-only
workaround given the sandbox lives inside the spawned CLI process itself.
- Sanity-check
computeAgentRoot's MAX_ANCESTOR_LEVELS ceiling against a
real layout like src/frontends/{root,mfes/*,shared/*} (root and its MFEs
share a grandparent two levels up) so a legitimate registration isn't
silently ignored as "too broad."
- Once released: no consumer-side change should be needed beyond bumping
the annotask dependency — the annotask({ mfe, server }) options and
POST /api/mfes/register call are already wired correctly on the plugin
side and were confirmed present in every MFE's vite.config.ts in the
consumer repo that hit this.
Workaround in the meantime
None found. The pnpm-workspace.yaml addition and the .annotask/agents.json
projectDirections update (telling the agent sibling MFEs exist) both help
the agent reason about where files live, but can't override the hard
process-level sandbox the spawned CLI itself enforces from cwd. Cross-MFE
edits currently require running a separate Annotask/agent session rooted at
(or above) the target MFE directly.
Embedded agent still sandboxed to a single MFE's projectRoot — cross-MFE
agentRootwork is implemented but not wired/releasedSummary
In a single-spa / micro-frontend setup where the root shell and every MFE each
run their own Vite dev server (
annotask({ mfe, server })pointed at the rootserver, per the docs), a task whose target file lives in a sibling MFE
still fails with a permission/access error when the embedded agent tries to
read or edit it — even though:
the root server on boot (
registerWithRootServer→POST /__annotask/api/mfes/register) via the documentedannotask({ mfe, server })options, andpnpm-workspace.yamlwas added at the monorepo root specifically to giveresolveWorkspace()a workspace root to discover.Neither fix helps, because the embedded coding-agent CLI (claude/codex/
opencode/copilot) is still spawned with
cwd: projectRoot— the singleMFE's own directory — not the broader
agentRootthe registry is designed tocompute. The spawned CLI enforces its own filesystem sandbox relative to that
cwd, so
bash/glob/viewcalls against../mfes/<sibling>/...are refusedby the CLI itself, independent of anything Annotask's own HTTP API permits.
annotask@0.4.5(latest published; confirmed via installednode_modules/annotaskin a consumer repo — zero references tomfe-registry,computeAgentRoot,mfes/register, oragentRootanywhere in
dist/)maincheckout):src/server/mfe-registry.ts,src/server/agent-spawn.ts,src/server/api.tsSteps to reproduce (consumer repo)
apps/root(shell, runs the Annotask server) +apps/mfes/<name>(several sibling MFEs), each MFE'svite.config.tspassing
annotask({ mfe: '@org/<name>', server: 'http://localhost:5174' })pointed at the root's dev server port.
cds into each package and runsvite).actually rendered by the sibling MFE (not the shell), and ask the embedded
agent to make an edit.
anchor / grounding data) but reports something like: "Access to
../mfes/<name>/is blocked in this sandbox — my tools only permitreads/writes within
<root's own projectRoot>and its children... everyattempt returns 'Permission denied'."
Expected
Per this repo's own
src/server/mfe-registry.tsdoc comment:i.e. the spawned CLI's
cwdshould be the computedagentRoot, not thesingle MFE's
projectRoot, whenever one or more MFEs have registered.Actual
src/server/agent-spawn.ts(handleSpawn) still spawns with a hardcodedcwd: projectRoot:maincheckout,this has actually already been fixed:
src/server/api.tsnow resolvesconst agentRoot = await getAgentRoot(options.projectRoot)and callsoptions.agentSpawn.handleSpawn(req, res, parsed.data, agentRoot)(api.ts~L1179-1180), so the spawn cwd is the computed agent root there.
src/server/mfe-registry.tsand its test file are untracked(
git statusshows??) — this work has not been committed, let alonereleased. It is not in
CHANGELOG.md(latest entry is0.4.4, whichpredates this file), and the installed
0.4.5tarball has none of it.above with no workaround —
pnpm-workspace.yaml/resolveWorkspace()only ever fed the component-scanning / path-containment side (already
present since 0.4.2's "Cross-MFE apply and byte-exact undo"), never the
actual agent spawn cwd.
Ask
src/server/mfe-registry.ts+ its test, and confirmapi.ts'sexisting
getAgentRoot(options.projectRoot)→handleSpawn(..., agentRoot)wiring is complete (it looks done from a read-through, but hasn't been
exercised against a real multi-MFE consumer since it's uncommitted).
get this fix any other way; there's no client-side or config-only
workaround given the sandbox lives inside the spawned CLI process itself.
computeAgentRoot'sMAX_ANCESTOR_LEVELSceiling against areal layout like
src/frontends/{root,mfes/*,shared/*}(root and its MFEsshare a grandparent two levels up) so a legitimate registration isn't
silently ignored as "too broad."
the
annotaskdependency — theannotask({ mfe, server })options andPOST /api/mfes/registercall are already wired correctly on the pluginside and were confirmed present in every MFE's
vite.config.tsin theconsumer repo that hit this.
Workaround in the meantime
None found. The
pnpm-workspace.yamladdition and the.annotask/agents.jsonprojectDirectionsupdate (telling the agent sibling MFEs exist) both helpthe agent reason about where files live, but can't override the hard
process-level sandbox the spawned CLI itself enforces from
cwd. Cross-MFEedits currently require running a separate Annotask/agent session rooted at
(or above) the target MFE directly.