[GH-ISSUE #6607] [Bug] identify the selected database in worker resource errors #3991

Closed
opened 2026-10-31 00:45:51 +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/6607

Before submitting

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

Area

CLI

Observed behavior

The public inspect-sqlite-database CLI correctly refuses an oversized selected row but omits the caller-selected database path from its complete JSON error. Independently created alpha.db and beta.db, each containing one 9 MiB BLOB, both exit 1 with empty stderr and byte-identical error documents. Both errors report resource_constraint, the correct reply-budget reason and the fixed byte allowances, but neither identifies its own selected file. A native SQLite value/record refusal also omits the path on the source baseline: an integrity-checked database containing a 17 MiB stored trigger comment returns string or blob too big.

Reproduction

From the source checkout identified below, with its locked dependencies and compiled runtime prepared, run the following block using the supported Node runtime. It creates disposable local fixtures and isolates the CLI's configuration and temporary directories. Every child invocation uses node scripts/rea.mjs inspect-sqlite-database <selected path> ... --json; no provider is substituted. The inline block was replayed independently.

node --input-type=module <<'JS'
import { DatabaseSync } from 'node:sqlite';
import { createHash } from 'node:crypto';
import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, truncateSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { spawnSync } from 'node:child_process';
const root = mkdtempSync(join(tmpdir(), 'sqlite-path-check-'));
for (const name of ['inputs', 'home', 'config', 'cache', 'runtime']) mkdirSync(join(root, name));
const inputs = join(root, 'inputs');
const path = name => join(inputs, `${name}.db`);
const large = new DatabaseSync(path('alpha'));
large.exec('CREATE TABLE records(id INTEGER PRIMARY KEY, payload BLOB)');
large.prepare('INSERT INTO records(payload) VALUES (?)').run(Buffer.alloc(9 * 1024 * 1024, 7));
large.close();
copyFileSync(path('alpha'), path('beta'));
const declaration = new DatabaseSync(path('declaration'));
declaration.exec('CREATE TABLE records(id INTEGER); CREATE TRIGGER stored_declaration AFTER INSERT ON records BEGIN SELECT 1; /*' + 'a'.repeat(17 * 1024 * 1024) + '*/ END;');
console.log({ declarationIntegrity: declaration.prepare('PRAGMA integrity_check').get().integrity_check });
declaration.close();
for (const [file, size] of [[path('snapshot'), 128 * 1024 * 1024], [path('snapshot') + '-wal', 128 * 1024 * 1024 + 1]]) {
  writeFileSync(file, '');
  truncateSync(file, size);
}
const small = new DatabaseSync(path('small'));
small.exec("CREATE TABLE records(payload TEXT); INSERT INTO records VALUES ('kept')");
small.close();
const fingerprint = () => readdirSync(inputs).sort().map(name => [name, createHash('sha256').update(readFileSync(join(inputs, name))).digest('hex')]);
const before = JSON.stringify(fingerprint());
const env = {
  HOME: join(root, 'home'), XDG_CONFIG_HOME: join(root, 'config'),
  XDG_CACHE_HOME: join(root, 'cache'), TMPDIR: join(root, 'runtime'),
  PATH: dirname(process.execPath) + ':/usr/bin:/bin', REA_LOG_LEVEL: 'silent',
};
const outputs = {};
for (const [name, selected, args] of [
  ['alpha', 'alpha', ['--table', 'records']],
  ['beta', 'beta', ['--table', 'records']],
  ['declaration', 'declaration', []],
  ['snapshot', 'snapshot', []],
  ['idControl', 'alpha', ['--table', 'records', '--column', 'id']],
  ['small', 'small', ['--table', 'records', '--column', 'payload']],
]) {
  const result = spawnSync(process.execPath, ['scripts/rea.mjs', 'inspect-sqlite-database', path(selected), ...args, '--json'], { env, encoding: 'utf8', timeout: 60000, maxBuffer: 1024 * 1024 });
  if (result.error) throw result.error;
  outputs[name] = result.stdout;
  const value = JSON.parse(result.stdout);
  console.log({ name, exit: result.status, stderr: result.stderr, hasSelectedPath: result.stdout.includes(path(selected)), code: value.code, reason: value.details?.reason, limits: value.details?.reported_limits, rows: value.normalized_result?.rows?.values });
}
console.log({ identicalErrors: outputs.alpha === outputs.beta, sourceFilesUnchanged: before === JSON.stringify(fingerprint()), runtimeEntries: readdirSync(join(root, 'runtime')) });
rmSync(root, { recursive: true, force: true });
JS

The two row-reply refusals and the native declaration refusal print exit: 1, stderr: '', hasSelectedPath: false, and code: 'resource_constraint'. The final comparison prints identicalErrors: true, sourceFilesUnchanged: true, and runtimeEntries: []. The snapshot admission control prints hasSelectedPath: true. Both successful controls exit 0: idControl returns integer 1 from the same large-row database, and small returns text kept.

Expected behavior

The SQLite failure contract promises the selected path and a meaningful reason for resource failures, including the documented worker value/record and reply limits. Each error should identify the database selected by that invocation. Preserve the resource refusal, its reason and its existing allowances; returning a partial or oversized successful result is not expected.

Impact and scope

P3 diagnostic-context defect. The response alone cannot distinguish the two selected files, but the refusal is correct and retaining each invocation's selected path alongside its response is a practical workaround. The reproduced source-run fixture hashes remained unchanged, and no incorrect successful result or loss of completed Evidence was observed. The sample establishes both distinct-path reply refusals and one native-value refusal on this host; it does not establish real-world frequency. Narrowing columns is a control for target readability, not recovery of the omitted BLOB.

REA version

Source baseline: bcd1f95e68, manifest version 6.7.0, with the prepared compiled CLI. Separately reproduced the distinct-path reply refusals using the actual published rea-agents 6.7.0 npm tarball's runtime and public launcher with prepared dependencies whose declared versions match the package. The native declaration case was executed on the source baseline only. No fresh global installation was performed.

Runtime and platform

Node.js v24.22.1; macOS 27.0.1 build 26A434; Darwin arm64; built-in SQLite 3.54.1. No external analysis provider was used.

Target and artifact context

Locally generated quiescent SQLite databases with an ordinary BLOB table, a separate database with one large stored trigger comment, and a small TEXT table. The declaration database passes native PRAGMA integrity_check. The sparse database/WAL pair totals 268435457 bytes and serves only as an input-budget admission control; its zero-filled content is not asserted to be a valid SQLite database.

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

  • Expected source: docs/sqlite-databases.md:108-109,162-164 documents the worker limits and promises the selected path for resource failures; src/sqlite/SqliteDatabaseSnapshot.ts:272-284 includes the selected path in the sibling snapshot-budget error.
  • Failure source: src/sqlite/SqliteDatabaseInspection.ts:100-112,590-595 refuses selected-row hex expansion; src/sqlite/SqliteDatabaseWorker.ts:33-65 serializes that refusal and native size failures; src/sqlite/SqliteDatabaseWorkerExecution.ts:109-132,245 receives the caller path but builds the resource error without it. The independently executed complete CLI documents for the two file selections are byte-identical and path-free.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

Both distinct-path row refusals contain the following fields; this excerpt omits the remaining recovery text and empty captured-output fields, while the inline comparison checks each complete stdout document.

{
  "code": "resource_constraint",
  "category": "resource_constraint",
  "message": "Selected row byte lengths exceed the 16 MiB reply budget before hex projection.",
  "retryable": false,
  "details": {
    "operation": "inspect_sqlite_database",
    "resource": "transport",
    "reason": "Selected row byte lengths exceed the 16 MiB reply budget before hex projection.",
    "reported_limits": {
      "sqlite_value_or_record_bytes": 16777216,
      "worker_reply_bytes": 16777216
    }
  }
}

The native declaration case has the same classification and allowances, with string or blob too big as its reason and message. The snapshot control instead includes its own selected path after Selected SQLite database and WAL total 268435457 bytes, exceeding the combined 268435456-byte offline snapshot budget:. No cleanup wrapper or process-inspection denial occurred in the full-access CLI reproductions; the isolated runtime directories were empty after the sequential runs completed.

Typed outcome or recovery expected

Keep resource_constraint, false retryability, the actual size-limit reason and existing numeric allowances, while making the caller-selected database path available in the error. Attribution should be possible without consulting a separately retained request. No larger allowance or partial success is requested.

Analysis or suspected cause

Source-derived: the worker resource-error branch forwards the refusal message and limits, and the later format-error branch adds the caller-selected path only for malformed-input issues. The outer service preserves the provider error. The public CLI observation establishes the omission independently of this explanation; the report does not prescribe a storage field or implementation.

Additional context

Open and closed issues were searched in the supplied inventory and live GitHub issue searches. Closed #5520 concerns missing paths on malformed-database refusals; that baseline control now includes its path while the resource error still does not. Closed #5456 concerns inapplicable retained-Evidence recovery advice; the baseline now correctly says this worker refusal creates no completed Evidence, while selected identity remains absent. Open #6292 concerns a result-column limit misclassified as a malformed database, and its reported error already includes the selected path. Those reports have distinct failing signals and restoration checks. MCP and other platforms were not executed. NOT_ENOUGH_INFO: no last-known-good release or real-world incidence was established.

Restoration check

Replay the inline fixture. Each of the two row-reply refusals and the native declaration refusal should identify its own caller-selected path while preserving the current refusal class, reason, limits and non-retryable status. Keep the snapshot control's path and exact size facts, both successful row controls, unchanged source fingerprints and empty runtime directory after completion. Adding only a private temporary snapshot path would not satisfy selected-input attribution. This check is proposed; no repair was implemented.

Triage assessment

  • Impact level: P3
  • Assessment status: supported
  • Impact basis: Correct resource refusals omit documented diagnostic identity and are byte-identical for two selected databases; no wrong successful result, source mutation or completed-result loss was observed.
  • Workaround status: available
  • Workaround basis: Each invocation selects one known database, so retaining that selected path or command with the response preserves attribution without altering the requested data or REA's safety limits.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6607 ## 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 CLI ## Observed behavior The public `inspect-sqlite-database` CLI correctly refuses an oversized selected row but omits the caller-selected database path from its complete JSON error. Independently created `alpha.db` and `beta.db`, each containing one 9 MiB BLOB, both exit 1 with empty stderr and byte-identical error documents. Both errors report `resource_constraint`, the correct reply-budget reason and the fixed byte allowances, but neither identifies its own selected file. A native SQLite value/record refusal also omits the path on the source baseline: an integrity-checked database containing a 17 MiB stored trigger comment returns `string or blob too big`. ## Reproduction From the source checkout identified below, with its locked dependencies and compiled runtime prepared, run the following block using the supported Node runtime. It creates disposable local fixtures and isolates the CLI's configuration and temporary directories. Every child invocation uses `node scripts/rea.mjs inspect-sqlite-database <selected path> ... --json`; no provider is substituted. The inline block was replayed independently. ```sh node --input-type=module <<'JS' import { DatabaseSync } from 'node:sqlite'; import { createHash } from 'node:crypto'; import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, truncateSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { dirname, join } from 'node:path'; import { spawnSync } from 'node:child_process'; const root = mkdtempSync(join(tmpdir(), 'sqlite-path-check-')); for (const name of ['inputs', 'home', 'config', 'cache', 'runtime']) mkdirSync(join(root, name)); const inputs = join(root, 'inputs'); const path = name => join(inputs, `${name}.db`); const large = new DatabaseSync(path('alpha')); large.exec('CREATE TABLE records(id INTEGER PRIMARY KEY, payload BLOB)'); large.prepare('INSERT INTO records(payload) VALUES (?)').run(Buffer.alloc(9 * 1024 * 1024, 7)); large.close(); copyFileSync(path('alpha'), path('beta')); const declaration = new DatabaseSync(path('declaration')); declaration.exec('CREATE TABLE records(id INTEGER); CREATE TRIGGER stored_declaration AFTER INSERT ON records BEGIN SELECT 1; /*' + 'a'.repeat(17 * 1024 * 1024) + '*/ END;'); console.log({ declarationIntegrity: declaration.prepare('PRAGMA integrity_check').get().integrity_check }); declaration.close(); for (const [file, size] of [[path('snapshot'), 128 * 1024 * 1024], [path('snapshot') + '-wal', 128 * 1024 * 1024 + 1]]) { writeFileSync(file, ''); truncateSync(file, size); } const small = new DatabaseSync(path('small')); small.exec("CREATE TABLE records(payload TEXT); INSERT INTO records VALUES ('kept')"); small.close(); const fingerprint = () => readdirSync(inputs).sort().map(name => [name, createHash('sha256').update(readFileSync(join(inputs, name))).digest('hex')]); const before = JSON.stringify(fingerprint()); const env = { HOME: join(root, 'home'), XDG_CONFIG_HOME: join(root, 'config'), XDG_CACHE_HOME: join(root, 'cache'), TMPDIR: join(root, 'runtime'), PATH: dirname(process.execPath) + ':/usr/bin:/bin', REA_LOG_LEVEL: 'silent', }; const outputs = {}; for (const [name, selected, args] of [ ['alpha', 'alpha', ['--table', 'records']], ['beta', 'beta', ['--table', 'records']], ['declaration', 'declaration', []], ['snapshot', 'snapshot', []], ['idControl', 'alpha', ['--table', 'records', '--column', 'id']], ['small', 'small', ['--table', 'records', '--column', 'payload']], ]) { const result = spawnSync(process.execPath, ['scripts/rea.mjs', 'inspect-sqlite-database', path(selected), ...args, '--json'], { env, encoding: 'utf8', timeout: 60000, maxBuffer: 1024 * 1024 }); if (result.error) throw result.error; outputs[name] = result.stdout; const value = JSON.parse(result.stdout); console.log({ name, exit: result.status, stderr: result.stderr, hasSelectedPath: result.stdout.includes(path(selected)), code: value.code, reason: value.details?.reason, limits: value.details?.reported_limits, rows: value.normalized_result?.rows?.values }); } console.log({ identicalErrors: outputs.alpha === outputs.beta, sourceFilesUnchanged: before === JSON.stringify(fingerprint()), runtimeEntries: readdirSync(join(root, 'runtime')) }); rmSync(root, { recursive: true, force: true }); JS ``` The two row-reply refusals and the native declaration refusal print `exit: 1`, `stderr: ''`, `hasSelectedPath: false`, and `code: 'resource_constraint'`. The final comparison prints `identicalErrors: true`, `sourceFilesUnchanged: true`, and `runtimeEntries: []`. The snapshot admission control prints `hasSelectedPath: true`. Both successful controls exit 0: `idControl` returns integer `1` from the same large-row database, and `small` returns text `kept`. ## Expected behavior The SQLite failure contract promises the selected path and a meaningful reason for resource failures, including the documented worker value/record and reply limits. Each error should identify the database selected by that invocation. Preserve the resource refusal, its reason and its existing allowances; returning a partial or oversized successful result is not expected. ## Impact and scope P3 diagnostic-context defect. The response alone cannot distinguish the two selected files, but the refusal is correct and retaining each invocation's selected path alongside its response is a practical workaround. The reproduced source-run fixture hashes remained unchanged, and no incorrect successful result or loss of completed Evidence was observed. The sample establishes both distinct-path reply refusals and one native-value refusal on this host; it does not establish real-world frequency. Narrowing columns is a control for target readability, not recovery of the omitted BLOB. ## REA version Source baseline: bcd1f95e688a2ac020a9d13f9c85e0ce321cb6b6, manifest version 6.7.0, with the prepared compiled CLI. Separately reproduced the distinct-path reply refusals using the actual published `rea-agents` 6.7.0 npm tarball's runtime and public launcher with prepared dependencies whose declared versions match the package. The native declaration case was executed on the source baseline only. No fresh global installation was performed. ## Runtime and platform Node.js v24.22.1; macOS 27.0.1 build 26A434; Darwin arm64; built-in SQLite 3.54.1. No external analysis provider was used. ## Target and artifact context Locally generated quiescent SQLite databases with an ordinary BLOB table, a separate database with one large stored trigger comment, and a small TEXT table. The declaration database passes native `PRAGMA integrity_check`. The sparse database/WAL pair totals 268435457 bytes and serves only as an input-budget admission control; its zero-filled content is not asserted to be a valid SQLite database. ## Interface CLI ## Earliest failure stage Transport or result projection ## Relevant output or evidence - Expected source: `docs/sqlite-databases.md:108-109,162-164` documents the worker limits and promises the selected path for resource failures; `src/sqlite/SqliteDatabaseSnapshot.ts:272-284` includes the selected path in the sibling snapshot-budget error. - Failure source: `src/sqlite/SqliteDatabaseInspection.ts:100-112,590-595` refuses selected-row hex expansion; `src/sqlite/SqliteDatabaseWorker.ts:33-65` serializes that refusal and native size failures; `src/sqlite/SqliteDatabaseWorkerExecution.ts:109-132,245` receives the caller path but builds the resource error without it. The independently executed complete CLI documents for the two file selections are byte-identical and path-free. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete Both distinct-path row refusals contain the following fields; this excerpt omits the remaining recovery text and empty captured-output fields, while the inline comparison checks each complete stdout document. ```json { "code": "resource_constraint", "category": "resource_constraint", "message": "Selected row byte lengths exceed the 16 MiB reply budget before hex projection.", "retryable": false, "details": { "operation": "inspect_sqlite_database", "resource": "transport", "reason": "Selected row byte lengths exceed the 16 MiB reply budget before hex projection.", "reported_limits": { "sqlite_value_or_record_bytes": 16777216, "worker_reply_bytes": 16777216 } } } ``` The native declaration case has the same classification and allowances, with `string or blob too big` as its reason and message. The snapshot control instead includes its own selected path after `Selected SQLite database and WAL total 268435457 bytes, exceeding the combined 268435456-byte offline snapshot budget:`. No cleanup wrapper or process-inspection denial occurred in the full-access CLI reproductions; the isolated runtime directories were empty after the sequential runs completed. ## Typed outcome or recovery expected Keep `resource_constraint`, false retryability, the actual size-limit reason and existing numeric allowances, while making the caller-selected database path available in the error. Attribution should be possible without consulting a separately retained request. No larger allowance or partial success is requested. ## Analysis or suspected cause Source-derived: the worker resource-error branch forwards the refusal message and limits, and the later format-error branch adds the caller-selected path only for malformed-input issues. The outer service preserves the provider error. The public CLI observation establishes the omission independently of this explanation; the report does not prescribe a storage field or implementation. ## Additional context Open and closed issues were searched in the supplied inventory and live GitHub issue searches. Closed [#5520](https://github.com/morluto/rea/issues/5520) concerns missing paths on malformed-database refusals; that baseline control now includes its path while the resource error still does not. Closed [#5456](https://github.com/morluto/rea/issues/5456) concerns inapplicable retained-Evidence recovery advice; the baseline now correctly says this worker refusal creates no completed Evidence, while selected identity remains absent. Open [#6292](https://github.com/morluto/rea/issues/6292) concerns a result-column limit misclassified as a malformed database, and its reported error already includes the selected path. Those reports have distinct failing signals and restoration checks. MCP and other platforms were not executed. NOT_ENOUGH_INFO: no last-known-good release or real-world incidence was established. ## Restoration check Replay the inline fixture. Each of the two row-reply refusals and the native declaration refusal should identify its own caller-selected path while preserving the current refusal class, reason, limits and non-retryable status. Keep the snapshot control's path and exact size facts, both successful row controls, unchanged source fingerprints and empty runtime directory after completion. Adding only a private temporary snapshot path would not satisfy selected-input attribution. This check is proposed; no repair was implemented. ## Triage assessment - Impact level: P3 - Assessment status: supported - Impact basis: Correct resource refusals omit documented diagnostic identity and are byte-identical for two selected databases; no wrong successful result, source mutation or completed-result loss was observed. - Workaround status: available - Workaround basis: Each invocation selects one known database, so retaining that selected path or command with the response preserves attribution without altering the requested data or REA's safety limits.
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#3991
No description provided.