ruvnet/ruflo · error
Pinata upload failed
Error message
Pinata upload failed (HTTP ${res.status}): ${JSON.stringify(res.data)} What it means
The POST to Pinata's /pinning/pinFileToIPFS returned a non-200 status; the message embeds both the code and Pinata's response body, so the exact cause is in the text. 401 means missing/invalid JWT, 402/403 plan or pin limits, 400 malformed multipart/oversized file, 429/5xx transient server-side conditions.
Solutions
- Read the embedded status: 401 → regenerate the JWT at pinata.cloud and update PINATA_API_JWT/config
- 402/403 → check plan and pin limits in the Pinata console; unpin old appliance versions to free slots
- 429/5xx → retry with exponential backoff; check status.pinata.cloud
- 400 → inspect the JSON body in the message; verify file size limits and the multipart metadata
Example fix
// before
const result = await publisher.publish(rvfaPath); // throws on first non-200
// after
try {
await publisher.publish(rvfaPath);
} catch (e) {
if (/HTTP 401/.test(String(e))) { /* refresh PINATA_API_JWT, then retry once */ }
else throw e;
} Defensive patterns
Strategy: retry
Validate before calling
if (!process.env.PINATA_API_JWT) throw new Error('PINATA_API_JWT missing');
const st = await stat(rvfaPath);
if (st.size > 100 * 1024 * 1024) {
throw new Error('File exceeds Pinata upload limit — split or compress it');
} Try / catch
async function publishWithRetry(p: RvfaPublisher, path: string, tries = 3) {
for (let attempt = 0; ; attempt++) {
try {
return await p.publish(path);
} catch (e) {
const msg = String(e);
if (/HTTP (429|5\d\d)/.test(msg) && attempt < tries - 1) {
await new Promise(r => setTimeout(r, 2 ** attempt * 1000)); // backoff, retry transient only
continue;
}
throw e; // 401/402/400 are configuration or payload errors — do not retry
}
}
} Prevention
- Alert on 401 responses so JWT rotation never silently breaks publishing
- Retry only 429/5xx with exponential backoff; treat 401/402/400 as fatal config/payload errors
- Keep pinned-content volume under the plan limit by unpinning old appliance versions
When it happens
Trigger: `publisher.publish(rvfaPath)` or the internal `upload()` with an expired/revoked JWT (401); free-plan pin quota exhausted (402/403); payload exceeding Pinata's upload limit or malformed metadata (400); Pinata incident (5xx).
Common situations: JWT rotated since deployment; enough artifacts pinned to hit plan caps; a custom apiUrl pointing at a deprecated endpoint; transient 5xx during publishing runs.
Related errors
- Pinata list failed (HTTP )
- Pinata pin failed (HTTP )
- Pinata JWT required (config.pinataJwt or PINATA_API_JWT)
- Pinata upload failed
- Local IPFS upload failed
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/ac96e1f1d8e83e52.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/cli/src/appliance/rvfa-distribution.ts:344
private jwt: string;
private gw: string;
private api: string;
constructor(config: PublishConfig) {
this.jwt = config.pinataJwt || process.env.PINATA_API_JWT || '';
this.gw = (config.gatewayUrl || DEFAULT_GW).replace(/\/+$/, '');
this.api = (config.apiUrl || DEFAULT_API).replace(/\/+$/, '');
if (!this.jwt) throw new Error('Pinata JWT required (config.pinataJwt or PINATA_API_JWT)');
}
private async upload(
fileName: string, data: Buffer, kv: Record<string, string>,
): Promise<PublishResult> {
const meta = JSON.stringify({ name: fileName, keyvalues: kv });
const { body, ct } = multipart('file', fileName, data, meta);
const res = await pinataReq('POST', `${this.api}/pinning/pinFileToIPFS`, this.jwt, body, ct);
if (res.status !== 200)
throw new Error(`Pinata upload failed (HTTP ${res.status}): ${JSON.stringify(res.data)}`);
const r = res.data as { IpfsHash: string; PinSize: number };
return {
cid: r.IpfsHash, size: r.PinSize,
gatewayUrl: `${this.gw}/ipfs/${r.IpfsHash}`, pinataUrl: `${this.api}/pinning/pins/${r.IpfsHash}`,
};
}
async publish(rvfaPath: string, meta?: PublishMetadata): Promise<PublishResult> {
const data = await readFile(rvfaPath);
const name = meta?.name || rvfaPath.split('/').pop() || 'appliance.rvf';
return this.upload(name, data, {
type: 'rvfa-appliance', version: meta?.version || '',
profile: meta?.profile || '', description: meta?.description || '',
});
}
async publishPatch(patchBuf: Buffer, meta?: PublishMetadata): Promise<PublishResult> {
const name = meta?.name || `patch-${Date.now()}.rvfp`;View on GitHub (pinned to fa13ee4ad6)