garrytan/gstack · error · Error
Cannot rollback: ${normalized} has fewer than 2 versions in
Error message
Cannot rollback: ${normalized} has fewer than 2 versions in ${scope} scope.
Cause: no prior version to roll back to.
Action: $B domain-skill rm to delete instead, or wait for a future revision to roll back from. What it means
Thrown by rollbackSkill() when fewer than 2 non-tombstoned version rows exist for the host in the given scope. Rollback re-emits the second-latest version as a new latest row, so it requires at least one prior version to target. With 0 or 1 versions there is nothing to roll back to.
Source
Thrown at browse/src/domain-skills.ts:385
use_count: 0,
flag_count: 0,
updated_ts: now,
};
await appendRow(globalFile(), globalRow);
return globalRow;
}
/**
* Rollback to a prior version (by sha256 OR previous version number).
* Re-emits the prior row as the latest, preserving the version counter monotonicity.
*/
export async function rollbackSkill(host: string, projectSlug: string, scope: SkillScope = 'project'): Promise<DomainSkillRow> {
const normalized = normalizeHost(host);
const file = scope === 'project' ? projectFile(projectSlug) : globalFile();
const rows = await readRows(file);
const matching = rows.filter((r) => r.host === normalized && r.scope === scope && !r.tombstone);
if (matching.length < 2) {
throw new Error(
`Cannot rollback: ${normalized} has fewer than 2 versions in ${scope} scope.\n` +
'Cause: no prior version to roll back to.\n' +
'Action: $B domain-skill rm to delete instead, or wait for a future revision to roll back from.'
);
}
// Sort by version desc; take second-latest as the rollback target.
matching.sort((a, b) => b.version - a.version);
const target = matching[1]!;
const newVersion = matching[0]!.version + 1;
const restored: DomainSkillRow = {
...target,
version: newVersion,
updated_ts: new Date().toISOString(),
};
await appendRow(file, restored);
return restored;
}
View on GitHub (pinned to 94993f7401)
Solutions
- Use '$B domain-skill rm' to delete the skill instead if you want to remove it
- Wait for a future writeSkill() revision to create a second version, then rollback to the first
- Check version history with '$B domain-skill list' to confirm how many versions exist
Defensive patterns
Strategy: validation
Validate before calling
// Check version count before rolling back
const rows = await readRows(scope === 'project' ? projectFile(projectSlug) : globalFile());
const matching = rows.filter(r => r.host === normalizeHost(host) && r.scope === scope && !r.tombstone);
if (matching.length < 2) {
console.error('Not enough versions to roll back. Use delete instead.');
return;
}
await rollbackSkill(host, projectSlug, scope); Type guard
function hasRollbackTarget(rows: DomainSkillRow[], host: string, scope: SkillScope): boolean {
return rows.filter(r => r.host === normalizeHost(host) && r.scope === scope && !r.tombstone).length >= 2;
} Prevention
- Track version count after each writeSkill call
- Only offer rollback in the UI when >= 2 versions exist
- Use deleteSkill for single-version skills instead of rollback
When it happens
Trigger: Calling rollbackSkill(host, projectSlug, scope) where rows.filter(host === normalized && scope === scope && !tombstone).length < 2.
Common situations: The skill was only written once (single version), or all prior versions were tombstoned or compacted away. The user expects an undo that has no target.
Related errors
- Save blocked: classifier flagged content as potential inject
- Cannot promote: no skill for ${normalized} in project ${proj
- Cannot promote: skill for ${normalized} is in state "${curre
- Cannot delete: no skill for ${normalized} in ${scope} scope.
AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12).
Data as JSON: /api/errors/6e7ae303b6fb54dc.
Report an issue: GitHub.