tobi/qmd · info

OUTSIDE_COLLECTION

OUTSIDE_COLLECTION

Error message

OUTSIDE_COLLECTION

What it means

During collection indexing (qmd collection add / update), a candidate file resolved to a real path outside the collection root directory (isPathInsideDir failed). The file is skipped and recorded with code OUTSIDE_COLLECTION — symlinks escaping the collection are the usual cause (getRealPath resolves them).

Source

Thrown at src/cli/qmd.ts:1962

    console.log("No files found matching pattern.");
    // Continue so the deactivation pass can mark previously indexed docs as inactive.
  }

  let indexed = 0, updated = 0, unchanged = 0, processed = 0;
  const skippedFiles: { file: string; code: string }[] = [];
  const seenPaths = new Set<string>();
  // Literal paths of every file in this scan. Passed to the legacy-path
  // migration so it never adopts a row that still belongs to a live file.
  const livePaths = new Set(files.map(f => f.replace(/\\/g, '/')));
  const startTime = Date.now();

  for (const relativeFile of files) {
    const filepath = getRealPath(resolve(resolvedPwd, relativeFile));
    // Store the literal relative path — handelize() is NOT applied at index time.
    const path = relativeFile.replace(/\\/g, '/');
    if (!isPathInsideDir(resolvedPwd, filepath)) {
      processed++;
      skippedFiles.push({ file: relativeFile, code: "OUTSIDE_COLLECTION" });
      progress.set((processed / total) * 100);
      continue;
    }
    seenPaths.add(path);

    let content: string;
    try {
      content = readFileSync(filepath, "utf-8");
    } catch (err) {
      // Skip files that can't be read (ETIMEDOUT, EAGAIN, EACCES, …) (#460)
      processed++;
      skippedFiles.push({ file: relativeFile, code: fsErrorCode(err) });
      progress.set((processed / total) * 100);
      continue;
    }

    // Skip empty files - nothing useful to index
    if (!content.trim()) {

View on GitHub (pinned to dbfd0b4736)

Solutions

  1. If the target should be indexed, add its real location as its own collection instead of a symlink
  2. Replace the symlink with the actual directory or copy files inside the root
  3. Ignore the skip — it is informational; the rest of the collection still indexes

Example fix

# before
~/notes/repo -> /home/me/src/repo   (symlink escapes collection)
# after
qmd collection add /home/me/src/repo --name repo   # index real path directly
Defensive patterns

Strategy: fallback

Validate before calling

const real = fs.realpathSync(file); if (isPathInsideDir(collectionRoot, real)) indexFile(file); else skip();

Type guard

const isInsideCollection = (root: string, f: string) => isPathInsideDir(root, fs.realpathSync(f));

Prevention

When it happens

Trigger: Indexing a directory containing a symlink whose target lives outside the collection root; files listed by the scanner whose realpath resolves elsewhere (e.g. symlinks into ~/.config or another collection's dir).

Common situations: Symlinked dotfile dirs (e.g. vim/nvim config symlinking to a repo), monorepo workspaces symlinked from a parent folder, or masked paths pointing at absolute targets.

Related errors


AI-assisted analysis of tobi/qmd@dbfd0b4736 (2026-08-28). Data as JSON: /api/errors/7f40dfc1f0c602e0. Report an issue: GitHub.