{"record":{"id":"450c124ef8f55a10","repo":"nexu-io/open-design","slug":"live-artifact-refresh-is-older-than-the-latest-com","errorCode":null,"errorMessage":"live artifact refresh is older than the latest committed refresh","messagePattern":"live artifact refresh is older than the latest committed refresh","errorType":"exception","errorClass":"LiveArtifactStaleRefreshError","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/live-artifacts/store.ts","lineNumber":839,"sourceCode":"\nexport async function markLiveArtifactRefreshCommitted(\n  options: MarkLiveArtifactRefreshCommittedOptions,\n): Promise<LiveArtifactRefreshState> {\n  const artifactId = validateLiveArtifactStorageId(options.artifactId);\n  const paths = await assertLiveArtifactRefreshLockScope(options.projectsRoot, options.projectId, artifactId);\n  const refreshOrdinal = parseRefreshOrdinal(options.refreshId);\n  const state = await readLiveArtifactRefreshState(paths, options.projectId, artifactId);\n  if (refreshOrdinal >= state.nextRefreshOrdinal) {\n    throw validationError('refreshId', 'live artifact refresh id has not been allocated');\n  }\n  if ((state.lastCommittedRefreshOrdinal ?? 0) >= refreshOrdinal) {\n    const staleOptions: { projectId: string; artifactId: string; refreshId: string; lastCommittedRefreshId?: string } = {\n      projectId: options.projectId,\n      artifactId,\n      refreshId: options.refreshId,\n    };\n    if (state.lastCommittedRefreshId !== undefined) staleOptions.lastCommittedRefreshId = state.lastCommittedRefreshId;\n    throw new LiveArtifactStaleRefreshError('live artifact refresh is older than the latest committed refresh', staleOptions);\n  }\n\n  const nextState: LiveArtifactRefreshState = {\n    ...state,\n    nextRefreshOrdinal: Math.max(state.nextRefreshOrdinal, refreshOrdinal + 1),\n    lastCommittedRefreshId: options.refreshId,\n    lastCommittedRefreshOrdinal: refreshOrdinal,\n  };\n  await writeLiveArtifactRefreshState(paths, nextState);\n  return nextState;\n}\n\nexport async function markLiveArtifactRefreshRunning(\n  options: MarkLiveArtifactRefreshRunningOptions,\n): Promise<LiveArtifactStoreRecord> {\n  const artifactId = validateLiveArtifactStorageId(options.artifactId);\n  const paths = await assertLiveArtifactRefreshLockScope(options.projectsRoot, options.projectId, artifactId);\n  const current = await readPersistedLiveArtifact(paths);","sourceCodeStart":821,"sourceCodeEnd":857,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/live-artifacts/store.ts#L821-L857","documentation":"Thrown as LiveArtifactStaleRefreshError by markLiveArtifactRefreshCommitted() when the supplied refreshId's ordinal is <= the last successfully committed ordinal. Once a newer refresh has been committed, older allocated-but-uncommitted ids are superseded and must not be committed — doing so would roll the artifact backwards. The error exposes the stale refreshId and the lastCommittedRefreshId so callers can re-sync.","triggerScenarios":"Caller acquires refresh-000002, then separately acquires and commits refresh-000003, then tries to commit the now-stale refresh-000002. Also when a retry fires after the original call already succeeded but the response was lost.","commonSituations":"At-least-once retry logic that re-sends a commit after a network blip; two workers both attempting to commit different refreshes of the same artifact; UI re-submitting a stale form after a newer refresh completed.","solutions":["Acquire a fresh refresh lock to get a new refreshId, then commit that — never reuse an ordinal older than the latest committed one.","Make commit calls idempotent at the caller by tracking the latest committed refreshId and short-circuiting retries that match it.","Inspect error.lastCommittedRefreshId to reconcile local state before retrying.","Serialize commits per artifact so a newer refresh cannot overtake an older in-flight one."],"exampleFix":"// before — reusing a stale id after a newer commit\nawait markLiveArtifactRefreshCommitted({ ..., refreshId: 'refresh-000002' }); // throws if 000003 committed\n\n// after — acquire a fresh lock first\nconst lock = await acquireLiveArtifactRefreshLock({ ... });\nawait markLiveArtifactRefreshCommitted({ ..., refreshId: lock.metadata.refreshId });","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"import { LiveArtifactStaleRefreshError } from './store';\n\nfunction isStaleRefresh(error: unknown): boolean {\n  return error instanceof LiveArtifactStaleRefreshError;\n}","tryCatchPattern":"import { markLiveArtifactRefreshCommitted, acquireLiveArtifactRefreshLock, LiveArtifactStaleRefreshError } from './store';\n\ntry {\n  await markLiveArtifactRefreshCommitted({ projectsRoot, projectId, artifactId, refreshId });\n} catch (error) {\n  if (error instanceof LiveArtifactStaleRefreshError) {\n    // Do NOT retry with the same refreshId — acquire a fresh lock and recommit.\n    const lock = await acquireLiveArtifactRefreshLock({ projectsRoot, projectId, artifactId });\n    await markLiveArtifactRefreshCommitted({ projectsRoot, projectId, artifactId, refreshId: lock.metadata.refreshId });\n    return;\n  }\n  throw error;\n}","preventionTips":["Never reuse a refreshId after a newer refresh has committed.","Track the latest committed refreshId client-side to short-circuit no-op retries.","Serialize commits per artifact to prevent overtaking."],"tags":["live-artifacts","refresh","concurrency","ordering","stale-state"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}