garrytan/gstack · error · Error
State name must be alphanumeric (a-z, 0-9, _, -)
Error message
State name must be alphanumeric (a-z, 0-9, _, -)
What it means
The `state` command sanitizes `name` against `^[a-zA-Z0-9_-]+$` (lines 930-932) before joining it into a filesystem path (`path.join(stateDir, name + '.json')`). This prevents path traversal (`../`) and weird filenames. Any other character — slash, dot, space, unicode — triggers the error.
Source
Thrown at browse/src/meta-commands.ts:932
// Handle --clear flag
if (args.includes('--clear')) {
for (const file of files) {
try { fs.unlinkSync(path.join(inboxDir, file)); } catch (err: any) { if (err?.code !== 'ENOENT') throw err; }
}
lines.push(`Cleared ${files.length} message${files.length === 1 ? '' : 's'}.`);
}
return lines.join('\n');
}
// ─── State ────────────────────────────────────────
case 'state': {
const [action, name] = args;
if (!action || !name) throw new Error('Usage: state save|load <name>');
// Sanitize name: alphanumeric + hyphens + underscores only
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
throw new Error('State name must be alphanumeric (a-z, 0-9, _, -)');
}
const config = resolveConfig();
const stateDir = path.join(config.stateDir, 'browse-states');
mkdirSecure(stateDir);
const statePath = path.join(stateDir, `${name}.json`);
if (action === 'save') {
const state = await bm.saveState();
// V1: cookies + URLs only (not localStorage — breaks on load-before-navigate)
const saveData = {
version: 1,
savedAt: new Date().toISOString(),
cookies: state.cookies,
pages: state.pages.map(p => ({ url: p.url, isActive: p.isActive })),
};
writeSecureFile(statePath, JSON.stringify(saveData, null, 2));
return `State saved: ${statePath} (${state.cookies.length} cookies, ${state.pages.length} pages)\n⚠️ Cookies stored in plaintext. Delete when no longer needed.`;View on GitHub (pinned to 94993f7401)
Solutions
- Use only letters, digits, underscore, and hyphen: `login-session`, `admin_v1`.
- Replace separators: use `-` or `_` instead of `/` or `.`.
- If you need hierarchy, encode it in the name (e.g. `tenantA-user`) rather than path segments.
Example fix
// before browse state save tenantA/user.v2 // after browse state save tenantA-user-v2
Defensive patterns
Strategy: validation
Validate before calling
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
throw new Error(`Invalid state name '${name}': use only a-z, 0-9, _, -`);
} Type guard
const isStateName = (s: string): boolean => /^[a-zA-Z0-9_-]+$/.test(s);
Prevention
- Generate state names from a known-safe charset (slugify free-form input).
- Never accept user-supplied paths into the name slot — only logical names.
When it happens
Trigger: `browse state save my/session`, `browse state save ../etc`, `browse state save session.2`, or any name containing spaces, dots, slashes, or non-ASCII.
Common situations: Trying to namespace states with slashes/dots, or passing a filename instead of a logical name.
Related errors
- Invalid skill name "${name}". Must be lowercase letters/digi
- Invalid file path in stageSkill: "${relPath}".
- Invalid state file: expected cookies and pages arrays
- Path must be within: ${SAFE_DIRECTORIES.join(', ')}
- Path must be within: ${TEMP_ONLY.join(', ')} (remote file se
AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12).
Data as JSON: /api/errors/3cdb28728f81a391.
Report an issue: GitHub.