mastra-ai/mastra · error · HTTPException

Mount "${requestedMount}" is read-only

Error message

Mount "${requestedMount}" is read-only

What it means

Thrown by buildSkillInstallPath when the requested mount exists in the CompositeFilesystem but is flagged readOnly. Skill installation writes files, so the server refuses with HTTP 403 rather than writing to a read-only mount. This is an intentional safety guard, not a transient failure.

Source

Thrown at packages/server/src/server/handlers/workspace.ts:215

 * For non-composite: returns `.agents/skills/<skillId>` (unchanged behavior).
 */
/** Strip a single trailing slash (leaves `/` alone). */
function stripTrailingSlash(p: string): string {
  return p.length > 1 && p.endsWith('/') ? p.slice(0, -1) : p;
}

function buildSkillInstallPath(filesystem: WorkspaceFilesystem, safeSkillId: string, requestedMount?: string): string {
  if (isCompositeFilesystem(filesystem)) {
    if (requestedMount) {
      // Validate the requested mount exists
      const mountFs = filesystem.mounts.get(requestedMount);
      if (!mountFs) {
        throw new HTTPException(400, {
          message: `Mount "${requestedMount}" not found. Available mounts: ${filesystem.mountPaths.join(', ')}`,
        });
      }
      if (mountFs.readOnly) {
        throw new HTTPException(403, { message: `Mount "${requestedMount}" is read-only` });
      }
      return `${stripTrailingSlash(requestedMount)}/${SKILLS_SH_DIR}/${safeSkillId}`;
    }

    // Default: use first writable mount
    for (const [mountPath, mountFs] of filesystem.mounts) {
      if (!mountFs.readOnly) {
        return `${stripTrailingSlash(mountPath)}/${SKILLS_SH_DIR}/${safeSkillId}`;
      }
    }

    throw new HTTPException(403, { message: 'No writable mount available for skill installation' });
  }

  // Non-composite: standard path
  return `${SKILLS_SH_DIR}/${safeSkillId}`;
}

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Retry against a writable mount from the workspace's mount list (omit the mount parameter to auto-select the first writable mount).
  2. If the mount should be writable, set readOnly: false for that mount in the CompositeFilesystem configuration and redeploy.
  3. If immutability is intended, change the install pipeline to a mount that is designated for skill writes.

Example fix

new CompositeFilesystem({
  mounts: {
    '/workspace': createFileSystem({ readOnly: false }), // before: readOnly: true
  },
})
Defensive patterns

Strategy: validation

Validate before calling

const mountInfo = await getWorkspaceMountInfo(workspaceId); // [{path, readOnly}]
const target = mountInfo.find(m => m.path === mount);
if (target?.readOnly) {
  throw new Error(`Mount "${mount}" is read-only; pick a writable mount`);
}

Type guard

function isWritableMount(m: { path: string; readOnly: boolean } | undefined): m is { path: string; readOnly: false } {
  return !!m && m.readOnly === false;
}

Try / catch

try {
  await installSkill({ workspaceId, skillId, mount });
} catch (e) {
  if (isHttpException(e, 403) && e.message.includes('is read-only')) {
    return installSkill({ workspaceId, skillId }); // auto-select first writable mount
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling the skill install/skillPath endpoints with a mount parameter that maps to a mount configured with readOnly: true in the CompositeFilesystem, or the mount's readOnly flag changed (e.g. after a safety-config update) while clients still target it.

Common situations: Deploying a workspace with mounts locked to read-only for production safety while CI pipelines still install skills to those mounts; sharing one workspace config where one team's mount is intentionally immutable.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/1b59d8597801e59b. Report an issue: GitHub.