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

  1. Read the embedded status: 401 → regenerate the JWT at pinata.cloud and update PINATA_API_JWT/config
  2. 402/403 → check plan and pin limits in the Pinata console; unpin old appliance versions to free slots
  3. 429/5xx → retry with exponential backoff; check status.pinata.cloud
  4. 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

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


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)