Skip to content

SDK strips directory paths from filenames in multipart uploads, breaking skills.versions.create() #968

@akhileshrangani4

Description

@akhileshrangani4

Description

The SDK's multipart form handling strips directory paths from filenames when uploading files. The getName function in internal/uploads.js is called with stripPath=true by default, which does val.split(/[\\/]/).pop(). This turns my-skill/SKILL.md into just SKILL.md.

This breaks client.beta.skills.versions.create() which requires SKILL.md to be inside a top-level directory (e.g. my-skill/SKILL.md). The API returns:

400 {"type":"error","error":{"type":"invalid_request_error","message":"SKILL.md file must be exactly in the top-level folder."}}

Interestingly, client.beta.skills.create() works fine with the stripped filename since it seems to infer the directory from the SKILL.md frontmatter. But versions.create() is stricter about requiring the directory in the filename.

Steps to Reproduce

import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic();

// 1. Create a skill (works fine, SDK strips path but API accepts it)
const skill = await client.beta.skills.create({
  display_title: 'my-skill',
  files: [new File(['---\nname: "my-skill"\n---\n\nInstructions here'], 'my-skill/SKILL.md', { type: 'text/markdown' })],
});

// 2. Create a new version (fails because SDK strips "my-skill/" from filename)
await client.beta.skills.versions.create(skill.id, {
  files: [new File(['---\nname: "my-skill"\n---\n\nUpdated instructions'], 'my-skill/SKILL.md', { type: 'text/markdown' })],
});
// Error: SKILL.md file must be exactly in the top-level folder.

Root Cause

In internal/uploads.js, multipartFormRequestOptions defaults stripFilenames to true:

const multipartFormRequestOptions = async (opts, fetch, stripFilenames = true) => {
    return { ...opts, body: await createForm(opts.body, fetch, stripFilenames) };
};

And addFormValue uses it to strip paths:

form.append(key, makeFile([value], getName(value, stripFilenames), { type: value.type }));

Where getName with stripPath=true does:

return stripPath ? val.split(/[\\/]/).pop() || undefined : val;

Expected Behavior

skills.versions.create() should preserve the directory path in the filename so the API receives my-skill/SKILL.md instead of just SKILL.md.

Workaround

We're currently working around this by deleting the old skill and creating a fresh one instead of using versions.create(). The OpenAI Node SDK has the same issue (openai/openai-node#1807).

Environment

  • @anthropic-ai/sdk version: 0.80.0
  • Node.js 22
  • Runtime: server-side (not browser)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions