{"record":{"id":"3d073d7898f85fb6","repo":"garrytan/gstack","slug":"commitskill-tier-opts-tier-has-no-resolved-p","errorCode":null,"errorMessage":"commitSkill: tier \"${opts.tier}\" has no resolved path.","messagePattern":"commitSkill: tier \"(.+?)\" has no resolved path\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"browse/src/browser-skill-write.ts","lineNumber":125,"sourceCode":" * Atomically move the staged skill into its final tier path. Refuses to\n * clobber an existing skill at the same path — the agent's approval gate\n * MUST surface name collisions before calling this.\n *\n * Returns the absolute path of the committed skill dir.\n *\n * Throws when:\n *   - tier path is unresolved (project tier with no project root)\n *   - destination already exists\n *   - staged dir is a symlink (refuses to follow)\n *   - resolved destination escapes the tier root (defense in depth)\n */\nexport function commitSkill(opts: CommitSkillOptions): string {\n  validateSkillName(opts.name);\n\n  const tiers = opts.tiers ?? defaultTierPaths();\n  const tierRoot = opts.tier === 'project' ? tiers.project : tiers.global;\n  if (!tierRoot) {\n    throw new Error(`commitSkill: tier \"${opts.tier}\" has no resolved path.`);\n  }\n\n  // Refuse to follow a symlinked staging dir — caller should hand us the path\n  // returned by stageSkill, which is always a real directory.\n  let stagedStat: fs.Stats;\n  try {\n    stagedStat = fs.lstatSync(opts.stagedDir);\n  } catch (err: any) {\n    throw new Error(`commitSkill: staged dir \"${opts.stagedDir}\" not accessible: ${err.code ?? err.message}`);\n  }\n  if (stagedStat.isSymbolicLink()) {\n    throw new Error(`commitSkill: staged dir \"${opts.stagedDir}\" is a symlink — refusing to commit.`);\n  }\n  if (!stagedStat.isDirectory()) {\n    throw new Error(`commitSkill: staged path \"${opts.stagedDir}\" is not a directory.`);\n  }\n\n  // Ensure the tier root exists, then resolve its real path so the final","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/browse/src/browser-skill-write.ts#L107-L143","documentation":"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'.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\ncommitSkill({ name: 'foo', tier: 'project', stagedDir });  // outside a git repo\n// after\ncommitSkill({ name: 'foo', tier: 'global', stagedDir });   // installs for the current user","handlingStrategy":"validation","validationCode":"import { defaultTierPaths } from './browser-skills';\n\nfunction assertTierResolvable(tier: 'project' | 'global', tiers = defaultTierPaths()): void {\n  const root = tier === 'project' ? tiers.project : tiers.global;\n  if (!root) {\n    throw new Error(`Tier \"${tier}\" unresolved. Run inside a git repo or use tier: 'global'.`);\n  }\n}\n// before commitSkill:\nassertTierResolvable(opts.tier);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["filesystem","skill-management","gstack","configuration"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}