garrytan/gstack · error · Error

Skill "${name}" has no script.test.ts at ${testFile}

Error message

Skill "${name}" has no script.test.ts at ${testFile}

What it means

Thrown by handleTest after the skill resolved successfully but path.join(skill.dir, 'script.test.ts') does not exist (fs.existsSync returned false). The skill is registered and its SKILL.md parses, but it has no bun-testable test file. Bundled or hand-written skills without tests hit this; /skillify-authored skills always include one.

Source

Thrown at browse/src/browser-skill-commands.ts:185

    const err = new Error(`Skill "${name}" failed: ${summary}\n--- stderr ---\n${result.stderr.slice(0, 4096)}`);
    (err as any).exitCode = result.exitCode || 1;
    throw err;
  }
  return result.stdout;
}

// ─── test ───────────────────────────────────────────────────────

async function handleTest(args: string[], ctx: SkillCommandContext): Promise<string> {
  const name = args[0];
  if (!name) throw new Error('Usage: $B skill test <name>');
  const tiers = ctx.tiers ?? defaultTierPaths();
  const skill = readBrowserSkill(name, tiers);
  if (!skill) throw new Error(`Skill "${name}" not found.`);

  const testFile = path.join(skill.dir, 'script.test.ts');
  if (!fs.existsSync(testFile)) {
    throw new Error(`Skill "${name}" has no script.test.ts at ${testFile}`);
  }

  const proc = Bun.spawn(['bun', 'test', testFile], {
    cwd: skill.dir,
    stdout: 'pipe',
    stderr: 'pipe',
    env: process.env,
  });
  const exitCode = await proc.exited;
  const stdout = proc.stdout ? await new Response(proc.stdout).text() : '';
  const stderr = proc.stderr ? await new Response(proc.stderr).text() : '';
  if (exitCode !== 0) {
    throw new Error(`Skill "${name}" tests failed (exit ${exitCode}).\n${stderr}`);
  }
  return stderr || stdout || `tests passed for "${name}"`;
}

// ─── rm ─────────────────────────────────────────────────────────

View on GitHub (pinned to 94993f7401)

Solutions

  1. Confirm the skill is meant to be testable; not all skills ship tests.
  2. If tests are expected, add `<skill.dir>/script.test.ts` (bun test format).
  3. Re-run /skillify to regenerate the skill with its test file.
  4. Restore the full directory from <tier>/.tombstones/ if the restore was partial.

Example fix

// before: <skill.dir>/ contains SKILL.md, script.ts only
// after: add <skill.dir>/script.test.ts
import { test, expect } from 'bun:test';
test('skill runs', async () => { expect(true).toBe(true); });
Defensive patterns

Strategy: validation

Validate before calling

import * as fs from 'fs';
import * as path from 'path';
import { readBrowserSkill } from './browser-skills';

function assertTestFile(name: string): string {
  const skill = readBrowserSkill(name);
  if (!skill) throw new Error(`Skill "${name}" not found.`);
  const testFile = path.join(skill.dir, 'script.test.ts');
  if (!fs.existsSync(testFile)) {
    throw new Error(`Skill "${name}" has no script.test.ts at ${testFile}`);
  }
  return testFile;
}

Prevention

When it happens

Trigger: handleTest for a skill whose directory lacks script.test.ts. Common for bundled skills shipped only with SKILL.md + script.ts, or for skills partially restored from a tombstone.

Common situations: User runs `$B skill test` on a skill that was never given tests; a skill was hand-authored without a test file; a /skillify run was interrupted after SKILL.md/script.ts were written but before script.test.ts landed; tombstone restore copied an incomplete tree.

Related errors


AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12). Data as JSON: /api/errors/980886505294fc05. Report an issue: GitHub.