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)
Description
The SDK's multipart form handling strips directory paths from filenames when uploading files. The
getNamefunction ininternal/uploads.jsis called withstripPath=trueby default, which doesval.split(/[\\/]/).pop(). This turnsmy-skill/SKILL.mdinto justSKILL.md.This breaks
client.beta.skills.versions.create()which requiresSKILL.mdto be inside a top-level directory (e.g.my-skill/SKILL.md). The API returns:Interestingly,
client.beta.skills.create()works fine with the stripped filename since it seems to infer the directory from the SKILL.md frontmatter. Butversions.create()is stricter about requiring the directory in the filename.Steps to Reproduce
Root Cause
In
internal/uploads.js,multipartFormRequestOptionsdefaultsstripFilenamestotrue:And
addFormValueuses it to strip paths:Where
getNamewithstripPath=truedoes:Expected Behavior
skills.versions.create()should preserve the directory path in the filename so the API receivesmy-skill/SKILL.mdinstead of justSKILL.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/sdkversion: 0.80.0