linshenkx/prompt-optimizer · error · ContextError

CONTEXT_ERROR_CODES.INVALID_STORE

CONTEXT_ERROR_CODES.INVALID_STORE

Error message

Invalid document structure

What it means

parseJsonObject successfully ran JSON.parse but the result failed the isRecord check — the parsed value is not a plain object (it's an array, string, number, boolean, or null). This inner error is deliberately re-thrown verbatim in the catch block so it isn't confused with a JSON syntax failure.

Source

Thrown at packages/core/src/services/context/repo.ts:142

      const doc: ContextStoreDoc = {
        version: CONTEXT_STORE_VERSION,
        currentId: DEFAULT_CONTEXT_CONFIG.id,
        contexts: {
          [DEFAULT_CONTEXT_CONFIG.id]: defaultContext
        }
      };

      // 立即保存初始文档
      await this.storage.setItem(CONTEXT_STORE_KEY, JSON.stringify(doc));
      return doc;
    }

    try {
      const doc = JSON.parse(data) as ContextStoreDoc;

      // 基础验证
      if (!doc.currentId || !doc.contexts || typeof doc.contexts !== 'object') {
        throw new ContextError(CONTEXT_ERROR_CODES.INVALID_STORE, 'Invalid document structure');
      }

      // 迁移逻辑:为旧文档的上下文补写 mode 字段
      let migrated = false;
      for (const ctx of Object.values(doc.contexts)) {
        if (!ctx.mode) {
          ctx.mode = DEFAULT_CONTEXT_MODE;
          migrated = true;
        }
      }

      // 如果有迁移,需要保存回存储
      if (migrated) {
        await this.storage.setItem(CONTEXT_STORE_KEY, JSON.stringify(doc));
        if (process.env.NODE_ENV === 'development') {
          console.log('[ContextRepo] Migrated contexts to add mode field');
        }
      }

View on GitHub (pinned to 3e677b1d9f)

Solutions

  1. Adjust the prompt to require a top-level JSON object with named keys
  2. If arrays are legitimately expected, accept them explicitly instead of relying on isRecord
  3. Fix the slice/extraction logic when it captures a non-object fragment

Example fix

// before
modelInstruction = 'Return the results as JSON.'

// after
modelInstruction =
  'Return the results as a single JSON OBJECT with keys "prompt" and "variables". Never return a top-level array.'
Defensive patterns

Strategy: type-guard

Validate before calling

function isJsonObjectText(text: string): boolean {
  try {
    return isRecord(JSON.parse(text))
  } catch {
    return false
  }
}

Type guard

function isRecord(v: unknown): v is Record<string, unknown> {
  return typeof v === 'object' && v !== null && !Array.isArray(v)
}

Try / catch

try {
  const parsed = parseJsonObject(candidate)
} catch (error) {
  if (error instanceof Error && error.message === 'Model response is not a valid JSON object') {
    // valid JSON but wrong shape (array/scalar) — prompt fix needed, retrying may help
    return coerceToObjectLikeShape(candidate)
  }
  throw error
}

Prevention

When it happens

Trigger: Calling the parse path with model output like '[1,2,3]', '"text"', '42', 'true', or 'null' — valid JSON whose top-level value is not a record.

Common situations: Model returns a JSON array instead of an object; model returns a bare string; extraction slice grabs a quoted string or number fragment from surrounding prose.

Related errors


AI-assisted analysis of linshenkx/prompt-optimizer@3e677b1d9f (2026-08-27). Data as JSON: /api/errors/b24e8749ce3b5d93. Report an issue: GitHub.