neoclide/coc.nvim · error · Error

main file ${main} not found, you may need to build the proje

Error message

main file ${main} not found, you may need to build the project.

What it means

Thrown by loadGlobalJsonAsync when the extension has no engines.vscode entry and its declared main entry file does not exist on disk. This usually means the extension source has not been compiled/built.

Source

Thrown at src/extension/stat.ts:285

function getEntryFile(main: string | undefined): string {
  if (!main) return 'index.js'
  if (!main.endsWith('.js')) return main + '.js'
  return main
}

export async function loadGlobalJsonAsync(folder: string, version: string): Promise<ExtensionJson> {
  let jsonFile = path.join(folder, 'package.json')
  let content = await readFile(jsonFile, 'utf8')
  let packageJSON = JSON.parse(content) as ExtensionJson
  let { engines } = packageJSON
  let main = getEntryFile(packageJSON.main)
  if (!engines || (typeof engines.coc !== 'string' && typeof engines.vscode !== 'string')) throw new Error('Invalid engines field')
  let keys = Object.keys(engines)
  if (keys.includes('coc') && !semver.satisfies(version, engines['coc'].replace(/^\^/, '>='))) {
    throw new Error(`coc.nvim version not match, required ${engines['coc']}`)
  }
  if (!engines.vscode && !fs.existsSync(path.join(folder, main))) {
    throw new Error(`main file ${main} not found, you may need to build the project.`)
  }
  return packageJSON
}

export function loadExtensionJson(folder: string, version: string, errors: string[]): ExtensionJson | undefined {
  let jsonFile = path.join(folder, 'package.json')
  if (!fs.existsSync(jsonFile)) {
    errors.push(`package.json not found in ${folder}`)
    return undefined
  }
  let packageJSON = loadJson(jsonFile) as ExtensionJson
  let { name, engines } = packageJSON
  let main = getEntryFile(packageJSON.main)
  if (!name) errors.push(`can't find name in package.json`)
  if (!engines || !objectLiteral(engines)) {
    errors.push(`invalid engines in ${jsonFile}`)
  }
  if (engines && !engines.vscode && !fs.existsSync(path.join(folder, main))) {

View on GitHub (pinned to 50e974d969)

Solutions

  1. Build the extension (npm install && npm run build / yarn build) to produce the main file
  2. Verify the `main` field in package.json matches the actual output file
  3. Reinstall the extension from a published release

Example fix

// before
ls my-ext # only src/, no lib/
// after
cd my-ext && npm install && npm run build # creates lib/index.js referenced by main
Defensive patterns

Strategy: validation

Validate before calling

const pkg = JSON.parse(fs.readFileSync(path.join(folder, 'package.json'), 'utf8'));
if (!pkg.engines?.vscode) {
  const main = pkg.main || 'index.js';
  if (!fs.existsSync(path.join(folder, main))) {
    console.error(`main file ${main} missing - build required`);
  }
}

Try / catch

try {
  await coc.extensions.load(folder, true);
} catch (e) {
  if (String(e.message).includes('main file') && String(e.message).includes('not found')) {
    console.error(`Build the extension in ${folder} (npm install && npm run build)`);
  } else throw e;
}

Prevention

When it happens

Trigger: Loading an extension folder where package.json main (e.g. lib/index.js) is missing because the project was never built, was partially installed, or main field is wrong.

Common situations: Cloning an extension from source and loading it without running npm install/build; missing dist/lib after CI packaging; TypeScript extension without compiled output.

Related errors


AI-assisted analysis of neoclide/coc.nvim@50e974d969 (2026-08-31). Data as JSON: /api/errors/661b8d6cfa19cfa8. Report an issue: GitHub.