BigPizzaV3/CodexPlusPlus · error · Error

待恢复批次与资料不匹配

Error message

待恢复批次与资料不匹配

What it means

During resume of an organize job, the loop maps the pending batch's stored partIds through partIndex. If any partId no longer resolves to a part (part is undefined), the persisted pending batch is inconsistent with the current history parts. The job aborts rather than silently skipping or absorbing wrong messages.

Solutions

  1. Discard the stale pending state and restart the organize job so nextBatch recomputes batches from current parts
  2. Verify the conversation history was not modified (messages deleted/edited) between pause and resume
  3. Upgrade consistently: run the same plugin version that created the persisted job when resuming
  4. Back up / re-export the thread and re-run organization from scratch if parts were re-indexed

Example fix

// before
const batch=job.pending?job.pending.partIds.map(id=>partIndex.get(id)):nextBatch(history.parts,job.state.done);
if(batch.some(part=>!part))throw Error('待恢复批次与资料不匹配');
// after
let batch=job.pending?job.pending.partIds.map(id=>partIndex.get(id)):nextBatch(history.parts,job.state.done);
if(batch.some(part=>!part)){ job.pending=null; batch=nextBatch(history.parts,job.state.done); }
Defensive patterns

Strategy: try-catch

Validate before calling

const allResolvable=job.pending?.partIds.every(id=>partIndex.has(id));
if(job.pending&&!allResolvable){ job.pending=null; /* recompute batches */ }

Type guard

function pendingMatchesParts(pending, partIndex){ return !!pending && Array.isArray(pending.partIds) && pending.partIds.every(id=>partIndex.has(id)); }

Try / catch

try { await organizeJob(...) } catch(e){ if(e.message==='待恢复批次与资料不匹配'){ await restartOrganizeJob(); } else throw e; }

Prevention

When it happens

Trigger: Resuming a saved/persisted organize job whose job.pending.partIds reference parts that were removed, re-split, or re-indexed because the conversation history changed between save and resume.

Common situations: User saves a paused organize job, the plugin version changes the part splitting logic or history is reloaded from a different thread snapshot, then resumes the job.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19). Data as JSON: /api/errors/17437c66c91ca0dd. Report an issue: GitHub.

Appendix: source

Thrown at tools/conversation-canvas/public/canvas.user.js:276

// coverage; a restart can resume its pending side-chat turn before resubmitting.
async function organizeLong({messages,record,save,run,signal,progress=()=>{},onGraph=()=>{},compact=false}){
  const history=await prepareHistory(messages,signal);
  const partIndex=new Map(history.parts.map(part=>[part.partId,part]));
  const previous=record?.published;
  let job=record?.job;
  if(!job||!compatible(job.state.manifest,history.manifest)){
    const incremental=previous?.done&&compatible(previous.manifest,history.manifest);
    const state=incremental?{...previous,manifest:history.manifest}:{schemaVersion:2,nodes:[],currentNodeId:null,batchCount:0,done:{},manifest:history.manifest};
    job={kind:incremental||!previous?.nodes?.length?'incremental':'rebuild',state,session:{},pending:null};
  }else job={...job,state:{...job.state,manifest:history.manifest}};
  let document={version:3,published:previous??null,job};
  const persist=async()=>{signal?.throwIfAborted();await save(structuredClone(document));};
  await persist();
  for(;;){
    signal?.throwIfAborted();
    // A resumed final batch must not absorb messages appended while paused.
    const batch=job.pending?job.pending.partIds.map(id=>partIndex.get(id)):nextBatch(history.parts,job.state.done);
    if(batch.some(part=>!part))throw Error('待恢复批次与资料不匹配');
    if(!batch.length)break;
    progress(`正在整理第 ${job.state.batchCount+1} 批 · 已处理 ${Object.keys(job.state.done).length}/${history.parts.length} 个片段`);
    if(!job.pending){job.pending={requestId:crypto.randomUUID(),partIds:batch.map(p=>p.partId),compact};await persist();}
    if(JSON.stringify(job.pending.partIds)!==JSON.stringify(batch.map(p=>p.partId)))throw Error('待恢复批次与资料不匹配');
    const {requestId}=job.pending,{catalog,prefix,aliases,prompt:basePrompt}=batchPrompt(batch,job.state,requestId,job.pending.compact);
    const repair=job.pending.repair;
    const missing=repair?.missingPartIds?.map(id=>aliases?Object.keys(aliases).find(key=>aliases[key]===id)||id:id);
    const prompt=basePrompt+(repair?`\n上次回复未通过覆盖校验,未保存任何本批节点。本次为第 ${repair.attempt}/2 次补正。上次漏引的 partId:${JSON.stringify(missing)}。请重新输出整个批次的完整 JSON,使用本次 requestId,覆盖本批全部 ${batch.length} 个片段(包括上次已引用的片段),不要只输出遗漏部分。发送前逐个核对 sources;重复讨论、进度说明也要归入有依据的任务节点。`: '');
    const response=await run(prompt,requestId,job.session,async()=>save(structuredClone(document)));
    signal?.throwIfAborted();
    let next;
    try{next=mergeBatch(response,job.state,batch,requestId,catalog,prefix,aliases);}
    catch(error){
      job.session.turnId=null;job.session.requestId=null;
      if(error.missingPartIds&&(repair?.attempt||0)<2){
        job.pending={requestId:crypto.randomUUID(),partIds:batch.map(p=>p.partId),compact:job.pending.compact,repair:{attempt:(repair?.attempt||0)+1,missingPartIds:error.missingPartIds}};
        await persist();progress(`本批遗漏 ${error.missingPartIds.length} 个片段,正在自动补正 ${job.pending.repair.attempt}/2…`);continue;
      }

View on GitHub (pinned to b1ed92e5e4)