nexu-io/open-design · error · Error

invalid live artifact id

Error message

invalid live artifact id

What it means

Thrown by validateLiveArtifactStorageId() when an artifactId fails the safe-id regex `/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/` or equals '.'/'..'. Every storage path derives from this id (it becomes a directory name under .live-artifacts/), so an unsafe value could escape the project dir or collide with filesystem metadata. The function is the single gate called by liveArtifactStorePaths(), getLiveArtifact(), and friends.

Source

Thrown at apps/daemon/src/live-artifacts/store.ts:294

export function generateLiveArtifactId(options: GenerateLiveArtifactIdOptions): string {
  const randomSuffix = options.randomSuffix ?? randomBytes(LIVE_ARTIFACT_ID_RANDOM_BYTES).toString('hex');
  if (!/^[a-f0-9]+$/i.test(randomSuffix) || randomSuffix.length === 0) {
    throw new Error('invalid live artifact id random suffix');
  }

  const suffix = randomSuffix.toLowerCase();
  const maxSlugLength = MAX_LIVE_ARTIFACT_STORAGE_ID_LENGTH - LIVE_ARTIFACT_ID_PREFIX.length - suffix.length - 2;
  if (maxSlugLength < 1) {
    throw new Error('invalid live artifact id random suffix');
  }
  const slug = truncateSlugAtSegmentBoundary(generateLiveArtifactSlug(options.slug ?? options.title), maxSlugLength);
  return validateLiveArtifactStorageId(`${LIVE_ARTIFACT_ID_PREFIX}-${slug}-${suffix}`);
}

export function validateLiveArtifactStorageId(artifactId: string): string {
  if (!SAFE_LIVE_ARTIFACT_ID.test(artifactId) || artifactId === '.' || artifactId === '..') {
    throw new Error('invalid live artifact id');
  }
  return artifactId;
}

export function liveArtifactsRootDir(projectsRoot: string, projectId: string): string {
  const projectDirPath = path.resolve(projectDir(projectsRoot, projectId));
  return resolveInside(projectDirPath, LIVE_ARTIFACTS_DIR_NAME, 'live artifact path escapes project dir');
}

export function liveArtifactStorePaths(
  projectsRoot: string,
  projectId: string,
  artifactId: string,
): LiveArtifactStorePaths {
  const safeArtifactId = validateLiveArtifactStorageId(artifactId);
  const projectDirPath = path.resolve(projectDir(projectsRoot, projectId));
  const rootDir = liveArtifactsRootDir(projectsRoot, projectId);
  const artifactDir = resolveInside(rootDir, safeArtifactId, 'live artifact path escapes storage root');

View on GitHub (pinned to 5be4028344)

Solutions

  1. Use ids returned by generateLiveArtifactId() — they always match the safe pattern (la-<slug>-<hexsuffix>).
  2. If you must accept external ids, validate with the same regex before calling store functions: `/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(id) && id !== '.' && id !== '..'`.
  3. Rename any externally-corrupted storage directory back to a valid id, or delete it.
  4. Do not pass paths, URLs, or slugs-with-spaces as artifactId; pass only the bare id.

Example fix

// before
getLiveArtifact({ projectsRoot, projectId, artifactId: 'my artifact/2' });
// after
getLiveArtifact({ projectsRoot, projectId, artifactId: 'la-my-artifact-9f2a1c8e0b7d' });
Defensive patterns

Strategy: validation

Validate before calling

import { validateLiveArtifactStorageId } from './store';

function safeArtifactId(id: string): string {
  try {
    return validateLiveArtifactStorageId(id);
  } catch {
    throw new Error(`Refusing unsafe artifact id: ${JSON.stringify(id)}`);
  }
}

// or inline at the trust boundary
const SAFE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
function isSafeArtifactId(id: string): boolean {
  return SAFE.test(id) && id !== '.' && id !== '..';
}

Type guard

function isLiveArtifactStorageId(value: unknown): value is string {
  return typeof value === 'string'
    && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value)
    && value !== '.'
    && value !== '..';
}

Prevention

When it happens

Trigger: Calling getLiveArtifact/update with a hand-crafted id containing '/', spaces, unicode, or exceeding 128 chars; an id of literally '.' or '..'; an id starting with a non-alphanumeric character like '-foo' or '_bar'; passing a URL or full path instead of the bare id.

Common situations: External client constructs an artifactId from user input or a URL slug without sanitizing; filesystem directory was renamed externally to an invalid name and listLiveArtifacts then re-validates each entry; migration tooling invents its own id scheme.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/b96bea8728dc74b4. Report an issue: GitHub.