janhq/jan · error

Jan data folder is unavailable

Error message

Jan data folder is unavailable

What it means

dataFolder resolves the Jan data directory via the service hub; if getJanDataFolder() returns a falsy value it throws. All skill store operations (list/read/write/delete/invoke) depend on this path, so they fail fast when the app data folder cannot be determined.

Solutions

  1. Ensure the app/backend services are fully initialized before using the skill store
  2. Call skill APIs only after the app is ready (await app readiness event)
  3. Check app logs for service hub initialization failures
  4. Verify the Jan data folder setting is present in app configuration

Example fix

// before
const skills = await listSkills({ kind: 'project', folder })
// after
const appService = getServiceHub().app()
if (!appService) throw new Error('App service not initialized')
const skills = await listSkills({ kind: 'project', folder })
Defensive patterns

Strategy: try-catch

Validate before calling

const folder = await getServiceHub().app().getJanDataFolder()
const ready = typeof folder === 'string' && folder.length > 0

Type guard

function hasDataFolder(f: unknown): f is string { return typeof f === 'string' && f.length > 0 }

Try / catch

try { const skills = await listSkills(scope) } catch (e) { if (String(e).includes('data folder is unavailable')) await waitForAppReady(); else throw e }

Prevention

When it happens

Trigger: Any of listSkills/readSkill/writeSkill/deleteSkill/invokeSkill called before the app service hub is ready or when getJanDataFolder() returns null/undefined/empty.

Common situations: Calling skill APIs too early during app startup, backend/app service not initialized, or corrupted app state where the data folder was never set.

Understand the failure class

Background: "missing required config value" errors: why libraries refuse to start when a configuration key is empty, unset, or blank — this error's family across 48 libraries.

Related errors


AI-assisted analysis of janhq/jan@7205d770c1 (2026-09-17). Data as JSON: /api/errors/442fb2d2eeff386a. Report an issue: GitHub.

Appendix: source

Thrown at web-app/src/lib/skillStore.ts:45

 * rather than the plugin's. They are not redundant: `agent_skill_write` also runs
 * `ensure_project`, which scaffolds `agent.toml`. That format is
 * owned by `core::agent::project`, and the plugin must not learn to write it --
 * owning no config format is what let the toolset be extracted at all. Routing
 * project writes through guest-js would silently drop the scaffold.
 */
export type SkillScope =
  | { kind: 'store' }
  | { kind: 'project'; folder: string }

export const storeScope: SkillScope = { kind: 'store' }
export const projectScope = (folder: string): SkillScope => ({
  kind: 'project',
  folder,
})

const dataFolder = async (): Promise<string> => {
  const folder = await getServiceHub().app().getJanDataFolder()
  if (!folder) throw new Error('Jan data folder is unavailable')
  return folder
}

export async function listSkills(scope: SkillScope): Promise<SkillMeta[]> {
  if (scope.kind === 'project') {
    return await invoke<SkillMeta[]>('agent_skill_list', {
      project: scope.folder,
    })
  }
  return await skillList(await dataFolder())
}

/** Raw SKILL.md text, frontmatter included. */
export async function readSkill(
  scope: SkillScope,
  name: string
): Promise<string> {
  if (scope.kind === 'project') {

View on GitHub (pinned to 7205d770c1)