garrytan/gstack · error · Error
commitSkill: tier "${opts.tier}" has no resolved path.
Error message
commitSkill: tier "${opts.tier}" has no resolved path. What it means
Thrown by commitSkill when the resolved tier root is null. tiers.project is null whenever detectProjectRoot failed (no git repo, git not on PATH, or projectRoot override not supplied) — so committing to the 'project' tier in a non-project context fails here. The global tier is always a string, so this error is only reachable for tier==='project'.
Source
Thrown at browse/src/browser-skill-write.ts:125
* Atomically move the staged skill into its final tier path. Refuses to
* clobber an existing skill at the same path — the agent's approval gate
* MUST surface name collisions before calling this.
*
* Returns the absolute path of the committed skill dir.
*
* Throws when:
* - tier path is unresolved (project tier with no project root)
* - destination already exists
* - staged dir is a symlink (refuses to follow)
* - resolved destination escapes the tier root (defense in depth)
*/
export function commitSkill(opts: CommitSkillOptions): string {
validateSkillName(opts.name);
const tiers = opts.tiers ?? defaultTierPaths();
const tierRoot = opts.tier === 'project' ? tiers.project : tiers.global;
if (!tierRoot) {
throw new Error(`commitSkill: tier "${opts.tier}" has no resolved path.`);
}
// Refuse to follow a symlinked staging dir — caller should hand us the path
// returned by stageSkill, which is always a real directory.
let stagedStat: fs.Stats;
try {
stagedStat = fs.lstatSync(opts.stagedDir);
} catch (err: any) {
throw new Error(`commitSkill: staged dir "${opts.stagedDir}" not accessible: ${err.code ?? err.message}`);
}
if (stagedStat.isSymbolicLink()) {
throw new Error(`commitSkill: staged dir "${opts.stagedDir}" is a symlink — refusing to commit.`);
}
if (!stagedStat.isDirectory()) {
throw new Error(`commitSkill: staged path "${opts.stagedDir}" is not a directory.`);
}
// Ensure the tier root exists, then resolve its real path so the finalView on GitHub (pinned to 94993f7401)
Solutions
- Run from inside the target git project so detectProjectRoot resolves.
- Commit to the global tier instead: pass tier: 'global' (places the skill in ~/.gstack/browser-skills/).
- Pass a synthetic tiers override with project set if calling programmatically in a non-project context.
- Verify `git rev-parse --show-toplevel` succeeds from the working directory.
Example fix
// before
commitSkill({ name: 'foo', tier: 'project', stagedDir }); // outside a git repo
// after
commitSkill({ name: 'foo', tier: 'global', stagedDir }); // installs for the current user Defensive patterns
Strategy: validation
Validate before calling
import { defaultTierPaths } from './browser-skills';
function assertTierResolvable(tier: 'project' | 'global', tiers = defaultTierPaths()): void {
const root = tier === 'project' ? tiers.project : tiers.global;
if (!root) {
throw new Error(`Tier "${tier}" unresolved. Run inside a git repo or use tier: 'global'.`);
}
}
// before commitSkill:
assertTierResolvable(opts.tier); Prevention
- Run /skillify from inside the target git project so the project tier resolves.
- Default to tier: 'global' for user-wide skills when project context is uncertain.
- Verify `git rev-parse --show-toplevel` succeeds in the working directory.
When it happens
Trigger: commitSkill({name, tier: 'project', stagedDir}) called outside a git repository; called in a context where git rev-parse failed (git missing, timeout, non-git dir); tests that didn't pass a synthetic tiers.project. Passing tier: 'global' never hits this because tiers.global is always resolved.
Common situations: Running /skillify from /tmp or a non-git working directory; CI runner that checks out code without .git; sandboxed environment without git installed; HOME override that broke path resolution in tests.
Related errors
- commitSkill: a skill named "${opts.name}" already exists at
- Skill "${name}" not found in any tier.
- Skill "${name}" not found.
- Skill "${name}" has no script.test.ts at ${testFile}
- Skill "${opts.skill.name}" missing script.ts at ${scriptPath
AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12).
Data as JSON: /api/errors/3d073d7898f85fb6.
Report an issue: GitHub.