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.

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.

Appendix: 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)