stablyai/orca · error · Error

Unable to create untitled markdown note

Error message

Unable to create untitled markdown note

What it means

Thrown after the mobile session screen exhausts 100 attempts to create an 'untitled.md' (or untitled-N.md) file in a worktree without finding a free slot. Each iteration calls the host's 'files.createFile' RPC and bails to this generic message only when the loop completes without a successful create-and-open pair. It is a fallback for the unlikely case that every untitled-1..100.md already exists or every create call failed with a non-'file exists' error that did not throw.

Source

Thrown at mobile/app/h/[hostId]/session/[worktreeId].tsx:3843

          const message = (createResponse as RpcFailure).error.message
          if (isFileExistsErrorMessage(message) && attempt < 100) {
            continue
          }
          throw new Error(message || 'Failed to create markdown note')
        }

        const openResponse = await client.sendRequest(
          'files.open',
          { worktree, relativePath },
          { timeoutMs: 15_000 }
        )
        if (!openResponse.ok) {
          throw new Error((openResponse as RpcFailure).error.message)
        }
        scheduleDelayedAction(() => void fetchSessionTabs(), 300)
        return
      }
      throw new Error('Unable to create untitled markdown note')
    } catch (err) {
      const message = err instanceof Error ? err.message : 'Failed to create markdown note'
      setCreateError(message)
      showToast(message, 1800)
    } finally {
      setCreatingMarkdown(false)
    }
  }

  async function handleCreateBrowser(rawUrl = 'about:blank'): Promise<boolean> {
    if (!client || creatingBrowser) {
      return false
    }
    // Why: read via ref so a tap before the capability probe resolves (or a stale callback) still sees the live value.
    if (browserScreencastSupportedRef.current !== true) {
      showToast('Desktop update required for mobile browser streaming', 1600)
      return false
    }

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Inspect the worktree directory and delete or rename the existing untitled-*.md files so a fresh slot is free.
  2. Check the host-side logs for the actual files.createFile errors; the generic message hides the per-attempt failure because non-'file exists' failures throw earlier with the host message.
  3. Verify the worktree is writable and not locked by another in-flight mutation (captureMobileFileMutationOwnership).
  4. If 100 slots is genuinely too few, raise the attempt ceiling or switch to a timestamp/UUID-based filename scheme.

Example fix

// before
for (let attempt = 1; attempt <= 100; attempt += 1) {
  const relativePath = attempt === 1 ? 'untitled.md' : `untitled-${attempt}.md`
  // ...
}
throw new Error('Unable to create untitled markdown note')

// after — include the last failure reason so the user can act on it
let lastReason = ''
for (let attempt = 1; attempt <= 100; attempt += 1) {
  const relativePath = attempt === 1 ? 'untitled.md' : `untitled-${attempt}.md`
  const createResponse = await client.sendRequest('files.createFile', { worktree, relativePath, ...mutationOwnership }, { timeoutMs: 15_000 })
  if (!createResponse.ok) {
    lastReason = (createResponse as RpcFailure).error.message
    if (isFileExistsErrorMessage(lastReason) && attempt < 100) continue
    throw new Error(lastReason || 'Failed to create markdown note')
  }
  // ...
  return
}
throw new Error(`Unable to create untitled markdown note: ${lastReason || 'all 100 slots taken'}`)
Defensive patterns

Strategy: try-catch

Validate before calling

// Before invoking, pre-check the directory is not pre-saturated
const existing = await client.sendRequest('files.list', { worktree, relativePath: '.' }, { timeoutMs: 15_000 })
if (existing.ok) {
  const names = (existing as RpcSuccess).result as { entries?: { name: string }[] }
  const taken = new Set((names.entries ?? []).map(e => e.name).filter(n => /^untitled(-\d+)?\.md$/.test(n)))
  // if all 100 slots taken, prompt user for a custom name instead of looping
}

Type guard

function isOpenResponseOk(r: RpcSuccess | RpcFailure): r is RpcSuccess {
  return r.ok
}

Try / catch

try {
  await handleCreateMarkdownNote()
} catch (err) {
  const message = err instanceof Error ? err.message : 'Failed to create markdown note'
  setCreateError(message)
  showToast(message, 1800)
} finally {
  setCreatingMarkdown(false)
}

Prevention

When it happens

Trigger: Reached only when `files.createFile` returns ok=false with a message that isFileExistsErrorMessage() recognizes 100 times in a row, or when createFile succeeds but the immediately-following `files.open` fails 100 times. The worktree is referenced as `id:${worktreeId}` via the client RPC.

Common situations: A worktree directory pre-populated with untitled.md through untitled-100.md (e.g. a test fixture, a generated docs folder, or a sync tool), or a host that rejects every createFile for reasons other than 'file exists' (permissions, read-only FS, worktree locked by another mutation).

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/849c0859d85a2322. Report an issue: GitHub.