vercel/ai · error · NoImageGeneratedError

No image generated.

Error message

No image generated.

What it means

Each skill's SKILL.md is written by the harness from the skill definition itself, so an attached file resolving to path 'SKILL.md' (after normalization) would collide with it. validateACPSkills rejects any attached file whose normalized path equals 'SKILL.md'.

Source

Thrown at packages/ai/src/generate-image/generate-image.ts:298

          ).images;
          if (Array.isArray(imagesValue) && imagesValue.length === 0) {
            delete (providerMetadata[providerName] as { images?: unknown })
              .images;
          }
        } else {
          providerMetadata[providerName] ??= { images: [] };
          providerMetadata[providerName].images.push(...metadata.images);
        }
      }
    }

    responses.push(result.response);
  }

  logWarnings({ warnings, provider: model.provider, model: model.modelId });

  if (!images.length) {
    throw new NoImageGeneratedError({ responses });
  }

  return new DefaultGenerateImageResult({
    images,
    calls,
    warnings,
    responses,
    providerMetadata,
    usage: totalUsage,
  });
}

class DefaultGenerateImageResult implements GenerateImageResult {
  readonly images: Array<GeneratedFile>;
  readonly calls: Array<GenerateImageCall>;
  readonly warnings: Array<Warning>;
  readonly responses: Array<ImageModelResponseMetadata>;
  readonly providerMetadata: ImageModelV4ProviderMetadata;

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Remove the attached SKILL.md entry and put its content in the skill's definition (the skill body/definition field) instead.
  2. Rename the extra document to something else, e.g. 'docs/NOTES.md'.
  3. Filter out any file whose normalized path is 'SKILL.md' before building the files array.

Example fix

// before
{ name: 'reviewer', definition: '...', files: [{ path: 'SKILL.md', data }] }

// after
{ name: 'reviewer', definition: data, files: [] }
Defensive patterns

Strategy: validation

Validate before calling

import path from 'node:path';
const RESERVED = new Set(['SKILL.md', './SKILL.md'].map(p => path.posix.normalize(p)));
skills.forEach(s => {
  s.files = (s.files ?? []).filter(f => !RESERVED.has(path.posix.normalize(f.path)));
});

Type guard

function isReservedSkillFile(p: string): boolean {
  return path.posix.normalize(p) === 'SKILL.md';
}

Try / catch

try {
  await agent.addSkills(skills);
} catch (error) {
  if (error instanceof Error && error.message.includes('SKILL.md is reserved')) {
    await agent.addSkills(skills.map(s => ({
      ...s,
      files: (s.files ?? []).filter(f => !isReservedSkillFile(f.path)),
    })));
  } else {
    throw error;
  }
}

Prevention

When it happens

Trigger: Including a file with path 'SKILL.md', './SKILL.md', or an equivalent in a skill's `files` array when the skill definition already supplies its own SKILL.md content.

Common situations: Bulk-copying an existing skills directory (which contains SKILL.md files) into the files array; templated loaders that attach every file in a folder, including SKILL.md; hand-writing extra docs and accidentally naming them SKILL.md.

Related errors


AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30). Data as JSON: /api/errors/20dda727d0cede0b. Report an issue: GitHub.