different-ai/openwork · error · ApiError

invalid_path

invalid_path

Error message

Only supported text artifact files can be read inline

What it means

The GET /workspace/:id/files/content endpoint reads a workspace file inline, but only for paths passing isSupportedWorkspaceTextFilePath() — i.e. recognized text artifact file types. Any other path (binary files, unknown extensions, dotfiles, no extension) is rejected with a 400 invalid_path ApiError before existence checks. This prevents dumping binaries or arbitrary files through the inline content API.

Source

Thrown at apps/server/src/routes/files.ts:842

        }

        items.push({ ok: false, type, code: "invalid_operation", message: `Unsupported operation type: ${type}` });
      } catch (error) {
        const message = error instanceof Error ? error.message : "Operation failed";
        items.push({ ok: false, type, code: "operation_failed", message });
      }
    }

    const events = fileSessions.listWorkspaceEvents(workspace.id, Number.MAX_SAFE_INTEGER);
    return jsonResponse({ items, cursor: events.cursor });
  });

  addRoute(routes, "GET", "/workspace/:id/files/content", "client", async (ctx) => {
    const workspace = await resolveWorkspace(config, ctx.params.id);
    const requested = ctx.url.searchParams.get("path") ?? "";
    const relativePath = normalizeWorkspaceRelativePath(requested, { allowSubdirs: true });
    if (!isSupportedWorkspaceTextFilePath(relativePath)) {
      throw new ApiError(400, "invalid_path", "Only supported text artifact files can be read inline");
    }

    const absPath = resolveSafeChildPath(workspace.path, relativePath);
    if (!(await exists(absPath))) {
      throw new ApiError(404, "file_not_found", "File not found");
    }
    const info = await stat(absPath);
    if (!info.isFile()) {
      throw new ApiError(404, "file_not_found", "File not found");
    }

    const maxBytes = FILE_SESSION_MAX_FILE_BYTES;
    if (info.size > maxBytes) {
      throw new ApiError(413, "file_too_large", "File exceeds size limit", { maxBytes, size: info.size });
    }

    const content = await readFile(absPath, "utf8");
    return jsonResponse({ path: relativePath, content, bytes: info.size, updatedAt: info.mtimeMs });

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Request only supported text artifact paths (e.g. *.md, *.ts, *.json — check isSupportedWorkspaceTextFilePath for the exact allowlist)
  2. Use the appropriate download/asset endpoint for binary files instead of the inline content API
  3. Fix the requested path's extension/case if it is actually a supported type
  4. List workspace files first and filter to supported text paths

Example fix

// before
const res = await api.get(`/workspace/${id}/files/content?path=logo.png`); // 400
// after
if (/\.(md|txt|ts|tsx|js|json|ya?ml|toml)$/.test(path)) {
  const res = await api.get(`/workspace/${id}/files/content?path=${encodeURIComponent(path)}`);
} else {
  const res = await api.get(`/workspace/${id}/files/download?path=${encodeURIComponent(path)}`);
}
Defensive patterns

Strategy: type-guard

Validate before calling

const SUPPORTED_TEXT_RE = /\.(md|txt|ts|tsx|js|jsx|json|ya?ml|toml|css|html|sh)$/i;
if (!SUPPORTED_TEXT_RE.test(relativePath)) {
  throw new Error(`use download endpoint for non-text file: ${relativePath}`);
}

Type guard

function isSupportedTextPath(p: string): boolean {
  return /\.(md|txt|ts|tsx|js|jsx|json|ya?ml|toml|css|html|sh)$/i.test(p);
}

Try / catch

try {
  const res = await api.get(`/workspace/${id}/files/content?path=${encodeURIComponent(p)}`);
} catch (e) {
  if (e.code === "invalid_path") {
    return api.get(`/workspace/${id}/files/download?path=${encodeURIComponent(p)}`); // binary route
  }
  throw e;
}

Prevention

When it happens

Trigger: Requesting content for a binary file (.png, .zip, .db), an unsupported extension (.exe, .pdf), a path with no extension, or a dotfile like .env; also a misspelled/mis-cased extension that fails the supported-type check.

Common situations: A UI tries to preview an uploaded image via the text-content endpoint; a script assumes any path under the workspace is readable; a symlink or generated file has an unexpected extension.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/702b01f463f3e484. Report an issue: GitHub.