[GH-ISSUE #6579] [Bug] cancelled full hashing omits the parsed GGUF alignment #3968

Closed
opened 2026-10-31 00:45:40 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @coygeek on GitHub (Oct 23, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/6579

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

The public CLI command node scripts/rea.mjs inspect-model-artifact FILE --hash full --format json, interrupted with SIGINT during full-file hashing, returns exit status 130 and code: "cancelled". Its details.partial_observation.normalized_result reports complete header, metadata and tensor coverage, but alignment is null although the same record contains the validated general.alignment metadata value 64. Three runs with the final fixture reproduced this result. Uncancelled controls return alignment: 64.

Reproduction

Use the built source checkout at the revision below with its locked dependencies and pinned Node toolchain. Save this self-contained driver as partial-alignment-repro.mjs in a disposable directory. Pass the checkout root and a separate disposable reproduction directory; all generated fixtures, isolated child configuration and output files stay inside that directory. Run:

node partial-alignment-repro.mjs CHECKOUT REPRODUCTION_DIRECTORY

The driver executes the public CLI, with no parser stubs or result substitution. It creates a 64 MiB sparse fixture for an uncancelled full-hash control and an 8 GiB sparse fixture for the exact-file header control and cancellation runs. Each cancelled child receives SIGINT after 1500 ms. Timing on another host may need adjustment: the distinguishing result requires coverage.header: "complete" and kind: "model-artifact-partial"; interruption before admission or after full completion does not exercise this case.

import { writeFile, truncate, mkdir } from 'node:fs/promises';
import { resolve, join } from 'node:path';
import { spawn } from 'node:child_process';
import assert from 'node:assert/strict';
const [checkout, root] = process.argv.slice(2).map(x => resolve(x));
await mkdir(join(root, 'home'), { recursive: true });
const env = Object.fromEntries(Object.entries(process.env).filter(([k]) => !/^(REA_|HOPPER_|GHIDRA_|JAVA_|JDK_)/.test(k) && !['CLAUDE_CONFIG_DIR','CODEX_HOME','COPILOT_HOME','OPENCODE_CONFIG','OPENCODE_CONFIG_DIR','XDG_CONFIG_HOME','XDG_CACHE_HOME','NODE_OPTIONS'].includes(k)));
Object.assign(env, { HOME: join(root, 'home'), XDG_CONFIG_HOME: join(root, 'home', 'config'), XDG_CACHE_HOME: join(root, 'home', 'cache'), NO_COLOR: '1' });
const u32 = n => { const b=Buffer.alloc(4); b.writeUInt32LE(n); return b; };
const u64 = n => { const b=Buffer.alloc(8); b.writeBigUInt64LE(BigInt(n)); return b; };
const str = s => Buffer.concat([u64(Buffer.byteLength(s)), Buffer.from(s)]);
const header = size => Buffer.concat([Buffer.from('GGUF'),u32(3),u64(1),u64(2),str('general.architecture'),u32(8),str('llama'),str('general.alignment'),u32(4),u32(64),str('weight'),u32(1),u64(size/4),u32(0),u64(0)]);
const fixtures=[];
for (const [name,size] of [['small',64*1024**2],['large',8*1024**3]]) {
 const h=header(size), file=join(root,`${name}.gguf`), dataOffset=Math.ceil(h.length/64)*64;
 await writeFile(file,h); await truncate(file,dataOffset+size);
 fixtures.push(file);
}
async function run(label, file, hash, cancel=false) {
 const args=[join(checkout,'scripts/rea.mjs'),'inspect-model-artifact',file,'--hash',hash,'--format','json'];
 const child=spawn(process.execPath,args,{cwd:checkout,env,stdio:['ignore','pipe','pipe']});
 let stdout='',stderr='';child.stdout.on('data',b=>stdout+=b);child.stderr.on('data',b=>stderr+=b);
 const timer=cancel?setTimeout(()=>child.kill('SIGINT'),1500):undefined;
 const [exitCode,signal]=await new Promise(r=>child.on('close',(...x)=>r(x)));clearTimeout(timer);
 await writeFile(join(root,`${label}.stdout.json`),stdout); await writeFile(join(root,`${label}.stderr.txt`),stderr);
 const result=JSON.parse(stdout);const report=(cancel?result.details?.partial_observation:result)?.normalized_result;
 const summary={label,exitCode,signal,code:result.code??null,kind:report?.kind??'model-artifact',coverage:report?.coverage,alignment:report?.alignment,metadata_alignment:report?.metadata?.find(r=>r.key==='general.alignment')?.value,sha256:report?.artifact.sha256??null};
 console.log(JSON.stringify(summary));
 assert.equal(exitCode,cancel?130:0);assert.equal(summary.metadata_alignment,64);
 if(cancel){assert.equal(summary.code,'cancelled');assert.equal(summary.kind,'model-artifact-partial');assert.equal(summary.coverage.header,'complete');assert.equal(summary.alignment,null);}else{assert.equal(summary.alignment,64); if(hash==='full') assert.match(summary.sha256,/^[a-f0-9]{64}$/);}
 return summary;
}
const summaries=[];
summaries.push(await run('header-control',fixtures[1],'header'));
summaries.push(await run('full-control',fixtures[0],'full'));
for(let i=1;i<=3;i++)summaries.push(await run(`cancel-${i}`,fixtures[1],'full',true));
await writeFile(join(root,'results.json'),JSON.stringify(summaries,null,2));

The driver exited 0, meaning its characterization assertions matched the current faulty behavior in all three final-fixture cancellation runs and both successful controls. Its cancellation assertion intentionally expects the observed null; it is not a restoration test.

Expected behavior

A typed cancellation should retain the already validated alignment as 64 once parsing has completed, consistently with the retained metadata row and successful inspection. GGUF inspection explicitly reports alignment, and the lifecycle contract requires retaining collected observations under cancellation. This does not require claiming a completed digest or verified final file identity: those remain unknown when full hashing is interrupted. A nullable field permits unknown facts; here the alignment is already known before hashing begins.

Impact and scope

P2: consumers of the normalized partial alignment field receive an unknown instead of the collected value. The loss is bounded to interrupted observations in the demonstrated case; neither a successful complete inspection nor the retained metadata row loses this value. A practical workaround was directly observed: read the general.alignment metadata row in the same cancellation response, or perform a header-only inspection of the unchanged selected file. Natural interruption frequency, other platforms, interruption during header parsing and downstream consumer effects were not measured. No data corruption or security-boundary crossing was observed.

REA version

Source revision bcd1f95e688a2ac020a9d13f9c85e0ce321cb6b6, using the prepared compiled runtime. This is a source-baseline finding, not a released-version claim. The latest published release is rea-agents-6.7.0; its tagged tree lacks this model-inspection command and implementation, so it is outside the affected scope demonstrated here.

Runtime and platform

Node.js v24.22.1; macOS 27.0.1 arm64. Existing compiled artifacts and locked dependencies were used without a build, installation or source modification. No external analysis engine or model runtime was used.

Target and artifact context

Synthetic little-endian GGUF v3 with general.architecture: "llama", general.alignment: 64, and one F32 tensor named weight. Its sole dimension matches the payload byte length divided by four; its relative tensor-data offset is zero and the data start is padded to 64 bytes. The large and small fixtures differ in declared dimension and sparse payload extent; payload values are never interpreted.

Interface

CLI

Earliest failure stage

Cleanup, cancellation, or lifecycle

Relevant output or evidence

  • Expected source: AGENTS.md, Architecture and Cleanup, requires preserving source values and partial facts; docs/tool-design.md:103-109 requires retaining collected observations under cancellation; docs/model-artifacts.md:42-47 includes alignment among GGUF facts; src/domain/model/modelArtifact.ts:100-124 includes the partial alignment field separately from unknown final identity and digests. The pinned official GGUF specification defines the explicit UINT32 alignment used by this fixture.
  • Failure source: actual public CLI results from the inline driver; src/model/GgufHeader.ts:282-325 validates the alignment before retaining its metadata row and returns it at lines 402-409; src/model/ModelArtifactProvider.ts:282-351 leaves the partial alignment initialized to null while marking header coverage complete before full hashing. Public routing is src/cli/modelCommands.ts:24-108 through src/application/model/ModelArtifactService.ts:34-61; the prepared compiled CLI contains the same omission.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

Selected output fields from each of the three final-fixture cancellation runs were:

{
  "exitCode": 130,
  "code": "cancelled",
  "kind": "model-artifact-partial",
  "coverage": { "header": "complete", "metadata": "complete", "tensors": "complete" },
  "alignment": null,
  "metadata_alignment": 64
}

These are the driver's diagnostic projections; metadata_alignment is extracted from the general.alignment row. Both controls exited 0 and returned alignment: 64 and metadata value 64. The full-hash control also returned a non-null SHA-256 digest. The cancellation responses retain null digests and unverified final identity, which are not the failure being reported.

Typed outcome or recovery expected

Keep the typed cancelled result, exit status 130 and partial Evidence. Within that Evidence, retain the resolved alignment for the completed parse, without promoting interrupted hashing to a successful inspection or claiming finalized artifact identity.

Analysis or suspected cause

Source-derived explanation: the parser owns the validated alignment and returns it with the completed header. The provider separately creates the partial record with an unknown alignment, updates other collected fields and complete-header coverage, then enters hashing without updating that field. When hashing is cancelled, the retained partial record therefore exposes its initial null value. The executed CLI observations establish the omission independently of this explanation; no implementation change is prescribed.

Additional context

Open and closed issue searches covered GGUF, alignment, general.alignment, and model partial/cancellation terms. Related closed #5625 reported losing all earlier metadata rows and returning no partialObservation when a later metadata array was interrupted. An independent source-boundary replay of that exact prior trigger now retains general.name: "First row", partial Evidence and the exact cancellation reason after reading the first 65536 bytes of the later 200000-element boolean array. That prior failure is restored. The present CLI case has retained rows and complete header coverage, with a distinct remaining failure signal: the normalized alignment is still null despite a known metadata value. This report does not claim that the original no-partialObservation defect regressed.

Closed #6095 concerns a missing committed evidence-bundle receipt after summary publication. This reproduction uses complete inline output and no evidence destination; cancellation occurs during hashing before publication. Closed #6004 concerns model host-error classification; this case returns the correct cancellation classification and loses a normalized fact instead. No exact duplicate was found. MCP cancellation was not executed, and the draft makes no observed MCP claim. No known-good revision for this alignment field has been established.

Restoration check

Proposed, not executed: rerun the same driver after a fix, replacing the characterization assertion that expects a null partial alignment with an assertion that expects 64. Each interrupted full hash should still return typed cancellation and exit 130, complete header coverage and the same retained metadata value, while digests and final identity remain unknown. Keep the uncancelled large-file header and small-file full-hash controls, including the latter's full digest, to ensure that changing interruption handling preserves successful inspection. The independent prior-issue array-cancellation control should continue retaining completed rows without claiming a complete header.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Three real CLI cancellation runs return an unknown normalized alignment after the header has validated and retained its value; this changes machine-readable partial evidence, with no demonstrated severe harm or security-boundary crossing.
  • Workaround status: available
  • Workaround basis: The same cancellation responses retain the explicit alignment metadata value, and a successful header-only inspection of the exact large fixture also returns alignment 64; both recovery paths were directly observed.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6579 ## Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ## Area Evidence, artifacts, or reconstruction ## Observed behavior The public CLI command `node scripts/rea.mjs inspect-model-artifact FILE --hash full --format json`, interrupted with SIGINT during full-file hashing, returns exit status 130 and `code: "cancelled"`. Its `details.partial_observation.normalized_result` reports complete header, metadata and tensor coverage, but `alignment` is `null` although the same record contains the validated `general.alignment` metadata value `64`. Three runs with the final fixture reproduced this result. Uncancelled controls return `alignment: 64`. ## Reproduction Use the built source checkout at the revision below with its locked dependencies and pinned Node toolchain. Save this self-contained driver as `partial-alignment-repro.mjs` in a disposable directory. Pass the checkout root and a separate disposable reproduction directory; all generated fixtures, isolated child configuration and output files stay inside that directory. Run: ```sh node partial-alignment-repro.mjs CHECKOUT REPRODUCTION_DIRECTORY ``` The driver executes the public CLI, with no parser stubs or result substitution. It creates a 64 MiB sparse fixture for an uncancelled full-hash control and an 8 GiB sparse fixture for the exact-file header control and cancellation runs. Each cancelled child receives SIGINT after 1500 ms. Timing on another host may need adjustment: the distinguishing result requires `coverage.header: "complete"` and `kind: "model-artifact-partial"`; interruption before admission or after full completion does not exercise this case. ```js import { writeFile, truncate, mkdir } from 'node:fs/promises'; import { resolve, join } from 'node:path'; import { spawn } from 'node:child_process'; import assert from 'node:assert/strict'; const [checkout, root] = process.argv.slice(2).map(x => resolve(x)); await mkdir(join(root, 'home'), { recursive: true }); const env = Object.fromEntries(Object.entries(process.env).filter(([k]) => !/^(REA_|HOPPER_|GHIDRA_|JAVA_|JDK_)/.test(k) && !['CLAUDE_CONFIG_DIR','CODEX_HOME','COPILOT_HOME','OPENCODE_CONFIG','OPENCODE_CONFIG_DIR','XDG_CONFIG_HOME','XDG_CACHE_HOME','NODE_OPTIONS'].includes(k))); Object.assign(env, { HOME: join(root, 'home'), XDG_CONFIG_HOME: join(root, 'home', 'config'), XDG_CACHE_HOME: join(root, 'home', 'cache'), NO_COLOR: '1' }); const u32 = n => { const b=Buffer.alloc(4); b.writeUInt32LE(n); return b; }; const u64 = n => { const b=Buffer.alloc(8); b.writeBigUInt64LE(BigInt(n)); return b; }; const str = s => Buffer.concat([u64(Buffer.byteLength(s)), Buffer.from(s)]); const header = size => Buffer.concat([Buffer.from('GGUF'),u32(3),u64(1),u64(2),str('general.architecture'),u32(8),str('llama'),str('general.alignment'),u32(4),u32(64),str('weight'),u32(1),u64(size/4),u32(0),u64(0)]); const fixtures=[]; for (const [name,size] of [['small',64*1024**2],['large',8*1024**3]]) { const h=header(size), file=join(root,`${name}.gguf`), dataOffset=Math.ceil(h.length/64)*64; await writeFile(file,h); await truncate(file,dataOffset+size); fixtures.push(file); } async function run(label, file, hash, cancel=false) { const args=[join(checkout,'scripts/rea.mjs'),'inspect-model-artifact',file,'--hash',hash,'--format','json']; const child=spawn(process.execPath,args,{cwd:checkout,env,stdio:['ignore','pipe','pipe']}); let stdout='',stderr='';child.stdout.on('data',b=>stdout+=b);child.stderr.on('data',b=>stderr+=b); const timer=cancel?setTimeout(()=>child.kill('SIGINT'),1500):undefined; const [exitCode,signal]=await new Promise(r=>child.on('close',(...x)=>r(x)));clearTimeout(timer); await writeFile(join(root,`${label}.stdout.json`),stdout); await writeFile(join(root,`${label}.stderr.txt`),stderr); const result=JSON.parse(stdout);const report=(cancel?result.details?.partial_observation:result)?.normalized_result; const summary={label,exitCode,signal,code:result.code??null,kind:report?.kind??'model-artifact',coverage:report?.coverage,alignment:report?.alignment,metadata_alignment:report?.metadata?.find(r=>r.key==='general.alignment')?.value,sha256:report?.artifact.sha256??null}; console.log(JSON.stringify(summary)); assert.equal(exitCode,cancel?130:0);assert.equal(summary.metadata_alignment,64); if(cancel){assert.equal(summary.code,'cancelled');assert.equal(summary.kind,'model-artifact-partial');assert.equal(summary.coverage.header,'complete');assert.equal(summary.alignment,null);}else{assert.equal(summary.alignment,64); if(hash==='full') assert.match(summary.sha256,/^[a-f0-9]{64}$/);} return summary; } const summaries=[]; summaries.push(await run('header-control',fixtures[1],'header')); summaries.push(await run('full-control',fixtures[0],'full')); for(let i=1;i<=3;i++)summaries.push(await run(`cancel-${i}`,fixtures[1],'full',true)); await writeFile(join(root,'results.json'),JSON.stringify(summaries,null,2)); ``` The driver exited 0, meaning its characterization assertions matched the current faulty behavior in all three final-fixture cancellation runs and both successful controls. Its cancellation assertion intentionally expects the observed `null`; it is not a restoration test. ## Expected behavior A typed cancellation should retain the already validated alignment as `64` once parsing has completed, consistently with the retained metadata row and successful inspection. GGUF inspection explicitly reports alignment, and the lifecycle contract requires retaining collected observations under cancellation. This does not require claiming a completed digest or verified final file identity: those remain unknown when full hashing is interrupted. A nullable field permits unknown facts; here the alignment is already known before hashing begins. ## Impact and scope P2: consumers of the normalized partial `alignment` field receive an unknown instead of the collected value. The loss is bounded to interrupted observations in the demonstrated case; neither a successful complete inspection nor the retained metadata row loses this value. A practical workaround was directly observed: read the `general.alignment` metadata row in the same cancellation response, or perform a header-only inspection of the unchanged selected file. Natural interruption frequency, other platforms, interruption during header parsing and downstream consumer effects were not measured. No data corruption or security-boundary crossing was observed. ## REA version Source revision `bcd1f95e688a2ac020a9d13f9c85e0ce321cb6b6`, using the prepared compiled runtime. This is a source-baseline finding, not a released-version claim. The latest published release is [rea-agents-6.7.0](https://github.com/morluto/rea/releases/tag/rea-agents-6.7.0); its tagged tree lacks this model-inspection command and implementation, so it is outside the affected scope demonstrated here. ## Runtime and platform Node.js v24.22.1; macOS 27.0.1 arm64. Existing compiled artifacts and locked dependencies were used without a build, installation or source modification. No external analysis engine or model runtime was used. ## Target and artifact context Synthetic little-endian GGUF v3 with `general.architecture: "llama"`, `general.alignment: 64`, and one F32 tensor named `weight`. Its sole dimension matches the payload byte length divided by four; its relative tensor-data offset is zero and the data start is padded to 64 bytes. The large and small fixtures differ in declared dimension and sparse payload extent; payload values are never interpreted. ## Interface CLI ## Earliest failure stage Cleanup, cancellation, or lifecycle ## Relevant output or evidence - Expected source: `AGENTS.md`, Architecture and Cleanup, requires preserving source values and partial facts; `docs/tool-design.md:103-109` requires retaining collected observations under cancellation; `docs/model-artifacts.md:42-47` includes alignment among GGUF facts; `src/domain/model/modelArtifact.ts:100-124` includes the partial alignment field separately from unknown final identity and digests. The [pinned official GGUF specification](https://github.com/ggml-org/ggml/blob/da8e2a54b919557b816b1c2beb432ccb766f7ce0/docs/gguf.md#general) defines the explicit UINT32 alignment used by this fixture. - Failure source: actual public CLI results from the inline driver; `src/model/GgufHeader.ts:282-325` validates the alignment before retaining its metadata row and returns it at lines 402-409; `src/model/ModelArtifactProvider.ts:282-351` leaves the partial alignment initialized to null while marking header coverage complete before full hashing. Public routing is `src/cli/modelCommands.ts:24-108` through `src/application/model/ModelArtifactService.ts:34-61`; the prepared compiled CLI contains the same omission. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete Selected output fields from each of the three final-fixture cancellation runs were: ```json { "exitCode": 130, "code": "cancelled", "kind": "model-artifact-partial", "coverage": { "header": "complete", "metadata": "complete", "tensors": "complete" }, "alignment": null, "metadata_alignment": 64 } ``` These are the driver's diagnostic projections; `metadata_alignment` is extracted from the `general.alignment` row. Both controls exited 0 and returned `alignment: 64` and metadata value `64`. The full-hash control also returned a non-null SHA-256 digest. The cancellation responses retain null digests and unverified final identity, which are not the failure being reported. ## Typed outcome or recovery expected Keep the typed `cancelled` result, exit status 130 and partial Evidence. Within that Evidence, retain the resolved alignment for the completed parse, without promoting interrupted hashing to a successful inspection or claiming finalized artifact identity. ## Analysis or suspected cause Source-derived explanation: the parser owns the validated alignment and returns it with the completed header. The provider separately creates the partial record with an unknown alignment, updates other collected fields and complete-header coverage, then enters hashing without updating that field. When hashing is cancelled, the retained partial record therefore exposes its initial null value. The executed CLI observations establish the omission independently of this explanation; no implementation change is prescribed. ## Additional context Open and closed issue searches covered GGUF, alignment, general.alignment, and model partial/cancellation terms. Related closed [#5625](https://github.com/morluto/rea/issues/5625) reported losing all earlier metadata rows and returning no partialObservation when a later metadata array was interrupted. An independent source-boundary replay of that exact prior trigger now retains `general.name: "First row"`, partial Evidence and the exact cancellation reason after reading the first 65536 bytes of the later 200000-element boolean array. That prior failure is restored. The present CLI case has retained rows and complete header coverage, with a distinct remaining failure signal: the normalized alignment is still null despite a known metadata value. This report does not claim that the original no-partialObservation defect regressed. Closed [#6095](https://github.com/morluto/rea/issues/6095) concerns a missing committed evidence-bundle receipt after summary publication. This reproduction uses complete inline output and no evidence destination; cancellation occurs during hashing before publication. Closed [#6004](https://github.com/morluto/rea/issues/6004) concerns model host-error classification; this case returns the correct cancellation classification and loses a normalized fact instead. No exact duplicate was found. MCP cancellation was not executed, and the draft makes no observed MCP claim. No known-good revision for this alignment field has been established. ## Restoration check Proposed, not executed: rerun the same driver after a fix, replacing the characterization assertion that expects a null partial alignment with an assertion that expects `64`. Each interrupted full hash should still return typed cancellation and exit 130, complete header coverage and the same retained metadata value, while digests and final identity remain unknown. Keep the uncancelled large-file header and small-file full-hash controls, including the latter's full digest, to ensure that changing interruption handling preserves successful inspection. The independent prior-issue array-cancellation control should continue retaining completed rows without claiming a complete header. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: Three real CLI cancellation runs return an unknown normalized alignment after the header has validated and retained its value; this changes machine-readable partial evidence, with no demonstrated severe harm or security-boundary crossing. - Workaround status: available - Workaround basis: The same cancellation responses retain the explicit alignment metadata value, and a successful header-only inspection of the exact large fixture also returns alignment 64; both recovery paths were directly observed.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#3968
No description provided.