garrytan/gstack · error

invalid_allowlist

invalid_allowlist

Error message

invalid_allowlist

What it means

Thrown by loadAllowlist() when the iOS-QA allowlist JSON file at ~/.gstack/ios-qa-allowlist.json (or GSTACK_IOS_ALLOWLIST_PATH) parses successfully but is structurally invalid: parsed.version is not exactly 1, or parsed.entries is not an array. It is a deliberate fail-closed guard: a half-written or hand-edited file must not silently pass as an empty list. ENOENT and empty files are tolerated earlier; this fires only after a successful JSON.parse.

Source

Thrown at ios-qa/daemon/src/allowlist.ts:38

  let raw: string;
  try {
    raw = await readFile(path, 'utf-8');
  } catch (err: unknown) {
    const e = err as { code?: string };
    if (e.code === 'ENOENT') {
      return { version: 1, entries: [] };
    }
    throw err;
  }
  // Empty-file path (mktemp default, partial write, manual `: > file`): treat
  // as "no entries yet" rather than a parse error. The first grant will fill
  // it in atomically via saveAllowlist.
  if (raw.trim() === '') {
    return { version: 1, entries: [] };
  }
  const parsed = JSON.parse(raw) as Allowlist;
  if (parsed.version !== 1 || !Array.isArray(parsed.entries)) {
    throw new Error('invalid_allowlist');
  }
  return parsed;
}

export async function saveAllowlist(allowlist: Allowlist, path: string = defaultAllowlistPath()): Promise<void> {
  await mkdir(dirname(path), { recursive: true, mode: 0o700 });
  await writeFile(path, JSON.stringify(allowlist, null, 2) + '\n', { mode: 0o600 });
}

/**
 * Look up an identity in the allowlist. Returns the entry if present AND
 * not expired. Lookup is exact-match on canonicalized identity.
 */
export function findEntry(allowlist: Allowlist, identity: string): AllowlistEntry | null {
  const now = Date.now();
  for (const entry of allowlist.entries) {
    if (entry.identity !== identity) continue;
    if (entry.expires_at) {

View on GitHub (pinned to 94993f7401)

Solutions

  1. Back up then delete ~/.gstack/ios-qa-allowlist.json and re-grant identities via gstack-ios-qa-mint grant.
  2. If you must edit by hand, ensure the shape is exactly {"version":1,"entries":[...]} with entries as an array.
  3. Do not bump version without updating loadAllowlist to handle the new schema.
  4. If the file was corrupted by a crash mid-write, restore from backup or regenerate.

Example fix

// before — corrupted file
{ "version": 2, "entries": {} }

// after — valid schema
{ "version": 1, "entries": [] }
Defensive patterns

Strategy: validation

Validate before calling

import { readFile } from 'fs/promises';
async function preflightAllowlist(path: string): Promise<void> {
  const raw = await readFile(path, 'utf-8');
  const parsed = JSON.parse(raw); // throws on parse error
  if (parsed.version !== 1 || !Array.isArray(parsed.entries)) {
    throw new Error(`Allowlist at ${path} has invalid shape; expected {version:1, entries:[]}`);
  }
}

Type guard

function isAllowlist(v: unknown): v is { version: number; entries: unknown[] } {
  return typeof v === 'object' && v !== null &&
    (v as any).version === 1 && Array.isArray((v as any).entries);
}

Try / catch

try {
  return await loadAllowlist(path);
} catch (e) {
  if (e instanceof Error && e.message === 'invalid_allowlist') {
    // Back up the corrupt file, start fresh
    await rename(path, path + '.bak');
    return { version: 1, entries: [] };
  }
  throw e;
}

Prevention

When it happens

Trigger: JSON.parse succeeds but the top-level object lacks version:1 or has a non-array entries field. Concretely: someone hand-edited the file and dropped a key; a partial write left valid JSON but wrong shape; a future schema migration wrote version:2 without a loader; entries is an object/number instead of an array.

Common situations: Manual edit of the allowlist that broke the schema; a botched migration; an editor auto-saved a truncated object; wrong file placed at the allowlist path.

Related errors


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