[GH-ISSUE #8844] [Bug] binary_session reports a registration mismatch for the same Bun entrypoint through symlink/parent traversal #7774

Open
opened 2026-11-04 00:49:11 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @coygeek on GitHub (Nov 4, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/8844

Before submitting

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

Area

MCP or agent integration

Observed behavior

Starting REA with Bun through an absolute symlink/.. spelling succeeds. Passing that exact launch spelling to binary_session.expected_server_path reports mcp_server_restart_required with registration_path_mismatch, although filesystem device/inode equality confirms it selects the same entrypoint file. Passing the canonical entrypoint path to the same live session returns aligned.

Observed on both the source revision below and the latest published rea-agents 6.8.0 package. In these actual Bun 1.4.4 runs, the returned server.command_path is canonical; it does not retain ...

Reproduction

Use Node.js 24.22.1 to run the client script and Bun 1.4.4 on PATH. From a built, otherwise unchanged checkout at the revision below, save this script as ../scratch/reproduce.mjs in a sibling scratch directory, then run node ../scratch/reproduce.mjs "$PWD". The script creates a disposable filesystem fixture and private HOME, launches the real packaged MCP entrypoint, and makes two requests on that same server. FIXTURE_ROOT can select a parent for the disposable fixture.

import { spawn } from 'node:child_process';
import { createInterface } from 'node:readline';
import { mkdirSync, mkdtempSync, statSync, symlinkSync } from 'node:fs';
import { join, resolve } from 'node:path';
import { tmpdir } from 'node:os';
const root = resolve(process.argv[2]);
const work = mkdtempSync(join(process.env.FIXTURE_ROOT ?? tmpdir(), 'rea-identity-'));
mkdirSync(join(work, 'home'));
symlinkSync(join(root, 'scripts'), join(work, 'link'));
const selected = `${work}/link/../scripts/rea.mjs`;
const canonical = `${root}/scripts/rea.mjs`;
const a = statSync(selected), b = statSync(canonical);
console.log(JSON.stringify({ same_file: a.dev === b.dev && a.ino === b.ino }));
const child = spawn('bun', [selected, 'mcp'], {
  cwd: work,
  env: { PATH: process.env.PATH, HOME: join(work, 'home'), TMPDIR: work,
    XDG_CONFIG_HOME: join(work, 'home'), XDG_CACHE_HOME: join(work, 'home'),
    REA_MCP_TOOL_FAMILIES: 'session' },
  stdio: ['pipe', 'pipe', 'inherit'],
});
const send = (message) => child.stdin.write(JSON.stringify({ jsonrpc: '2.0', ...message }) + '\n');
const timer = setTimeout(() => { child.kill('SIGTERM'); process.exitCode = 1; }, 25000);
const lines = createInterface({ input: child.stdout });
lines.on('line', (line) => {
  const message = JSON.parse(line);
  if (message.id === 1) {
    send({ method: 'notifications/initialized' });
    send({ id: 2, method: 'tools/call', params: {
      name: 'binary_session', arguments: { expected_server_path: selected } } });
  } else if (message.id === 2 || message.id === 3) {
    const identity = message.result.structuredContent.result.server_identity;
    console.log(JSON.stringify({ case: message.id === 2 ? 'launch-spelling' : 'canonical-control',
      alignment: identity.alignment }));
    if (message.id === 2) send({ id: 3, method: 'tools/call', params: {
      name: 'binary_session', arguments: { expected_server_path: canonical } } });
    else child.stdin.end();
  }
});
child.on('exit', (code) => { clearTimeout(timer); process.exitCode = code ?? 1; });
send({ id: 1, method: 'initialize', params: { protocolVersion: '2025-11-25',
  capabilities: {}, clientInfo: { name: 'identity-verifier', version: '1' } } });

The reduced script reproduced once against the built checkout and once against the installed published package after the focused tests. A separate control probe also reproduced the failure on both targets. No REA source, exports, argv values, or provider responses were patched or simulated.

For a published package, pass the absolute directory containing its package.json as the script argument instead of the checkout root. The installed package was obtained with npm install --ignore-scripts at version 6.8.0.

Expected behavior

The absolute filesystem path used to launch this server should not be declared a stale registration merely because lexical normalization changes its meaning. Establish comparable file identity before reporting alignment or mismatch, or preserve unknown with an explanation when the comparison cannot be established. Restart advice should correspond to an actual identity disagreement.

Expected source: the input contract accepts an absolute local filesystem path, and identity/discovery documentation says it compares the caller's expectation with the live session. The caller-path owner documents why POSIX symlink/.. traversal must not be replaced by lexical normalization; the repository invariants require preserving caller-selected paths and uncertainty when normalization loses information.

Impact and scope

P2: a valid same-file expectation produces an incorrect stale-registration result and unnecessary restart guidance. The demonstrated case is a target-free macOS Bun server launched through a directory symlink followed by ... No analysis failure, automatic restart, target mutation, or data loss was observed. Real-world frequency, other Bun versions, Linux, and Windows are unverified.

A practical workaround was observed: use the canonical server.command_path returned by this live session as the expectation. The same session then reports aligned.

REA version

rea-agents 6.8.0; source revision c3647ade1e557c4179a8b825a37e0c2193cd2526. rea --version on the installed package returned 6.8.0. npm reported 6.8.0 as the latest published release, and fetched origin/main matched this source revision.

Runtime and platform

macOS 27.2, arm64. Bun 1.4.4 for the server; Node.js v24.22.1 for the MCP client and focused tests. No analysis provider was needed.

Target and artifact context

No analysis target. The fixture is a symlink to the package's existing scripts directory and a launch spelling ending in link/../scripts/rea.mjs.

Interface

MCP

Earliest failure stage

Transport or result projection

Relevant output or evidence

The reduced script produced the following on both the built source checkout and the installed release:

{"same_file":true}
{"case":"launch-spelling","alignment":{"state":"mcp_server_restart_required","reasons":["registration_path_mismatch"],"remediation":"Restart the registered MCP server/client, then compare identity again."}}
{"case":"canonical-control","alignment":{"state":"aligned","reasons":[],"remediation":null}}

The larger real-MCP control probe also established: no expectation returns unknown; an unrelated absolute path returns registration_path_mismatch; a relative expectation is rejected; canonical Bun and Node launch/expectation pairs return aligned. MCP initialization succeeded, and all server processes exited zero with no stderr.

The focused command npm run test:focused -- tests/boundary/mcp/serverIdentity.test.ts src/contracts/sessionPathInputs.test.ts passed 25 tests with 3 skips across 2 files. Those existing tests do not establish correctness for this symlink/parent fixture.

Typed outcome or recovery expected

Report aligned when the two filesystem selections can be established as the same entrypoint; otherwise retain unknown rather than a definite path mismatch based on a meaning-changing normalization. Preserve genuine mismatched-path detection.

Analysis or suspected cause

Source-derived: the status handler forwards the admitted expectation and current entrypoint into createServerIdentity. That function compares resolve(expected.server_path) to the observed command-path string. In the executed fixture, the expected path's symlink/parent traversal reaches the running package, while lexical resolve produces a different spelling. There is no filesystem identity check or uncertainty projection in this comparison. This explains the observed mismatch; no broader runtime-path preservation claim is needed.

Additional context

Related but distinct: closed #115 describes the identity/restart feature; closed #7757 and #4899 concern setup registration binding and unresolved launcher identity. Open #5213 and #6410 fix Windows identity test fixtures without changing this production comparison. Open #6923 concerns Bun compatibility and setup launcher recognition. No exact open duplicate was found in the performed issue/PR searches.

Originally created by @coygeek on GitHub (Nov 4, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/8844 ### 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 MCP or agent integration ### Observed behavior Starting REA with Bun through an absolute symlink/`..` spelling succeeds. Passing that exact launch spelling to `binary_session.expected_server_path` reports `mcp_server_restart_required` with `registration_path_mismatch`, although filesystem device/inode equality confirms it selects the same entrypoint file. Passing the canonical entrypoint path to the same live session returns `aligned`. Observed on both the source revision below and the latest published rea-agents 6.8.0 package. In these actual Bun 1.4.4 runs, the returned `server.command_path` is canonical; it does not retain `..`. ### Reproduction Use Node.js 24.22.1 to run the client script and Bun 1.4.4 on PATH. From a built, otherwise unchanged checkout at the revision below, save this script as `../scratch/reproduce.mjs` in a sibling scratch directory, then run `node ../scratch/reproduce.mjs "$PWD"`. The script creates a disposable filesystem fixture and private HOME, launches the real packaged MCP entrypoint, and makes two requests on that same server. `FIXTURE_ROOT` can select a parent for the disposable fixture. ```js import { spawn } from 'node:child_process'; import { createInterface } from 'node:readline'; import { mkdirSync, mkdtempSync, statSync, symlinkSync } from 'node:fs'; import { join, resolve } from 'node:path'; import { tmpdir } from 'node:os'; const root = resolve(process.argv[2]); const work = mkdtempSync(join(process.env.FIXTURE_ROOT ?? tmpdir(), 'rea-identity-')); mkdirSync(join(work, 'home')); symlinkSync(join(root, 'scripts'), join(work, 'link')); const selected = `${work}/link/../scripts/rea.mjs`; const canonical = `${root}/scripts/rea.mjs`; const a = statSync(selected), b = statSync(canonical); console.log(JSON.stringify({ same_file: a.dev === b.dev && a.ino === b.ino })); const child = spawn('bun', [selected, 'mcp'], { cwd: work, env: { PATH: process.env.PATH, HOME: join(work, 'home'), TMPDIR: work, XDG_CONFIG_HOME: join(work, 'home'), XDG_CACHE_HOME: join(work, 'home'), REA_MCP_TOOL_FAMILIES: 'session' }, stdio: ['pipe', 'pipe', 'inherit'], }); const send = (message) => child.stdin.write(JSON.stringify({ jsonrpc: '2.0', ...message }) + '\n'); const timer = setTimeout(() => { child.kill('SIGTERM'); process.exitCode = 1; }, 25000); const lines = createInterface({ input: child.stdout }); lines.on('line', (line) => { const message = JSON.parse(line); if (message.id === 1) { send({ method: 'notifications/initialized' }); send({ id: 2, method: 'tools/call', params: { name: 'binary_session', arguments: { expected_server_path: selected } } }); } else if (message.id === 2 || message.id === 3) { const identity = message.result.structuredContent.result.server_identity; console.log(JSON.stringify({ case: message.id === 2 ? 'launch-spelling' : 'canonical-control', alignment: identity.alignment })); if (message.id === 2) send({ id: 3, method: 'tools/call', params: { name: 'binary_session', arguments: { expected_server_path: canonical } } }); else child.stdin.end(); } }); child.on('exit', (code) => { clearTimeout(timer); process.exitCode = code ?? 1; }); send({ id: 1, method: 'initialize', params: { protocolVersion: '2025-11-25', capabilities: {}, clientInfo: { name: 'identity-verifier', version: '1' } } }); ``` The reduced script reproduced once against the built checkout and once against the installed published package after the focused tests. A separate control probe also reproduced the failure on both targets. No REA source, exports, argv values, or provider responses were patched or simulated. For a published package, pass the absolute directory containing its `package.json` as the script argument instead of the checkout root. The installed package was obtained with `npm install --ignore-scripts` at version 6.8.0. ### Expected behavior The absolute filesystem path used to launch this server should not be declared a stale registration merely because lexical normalization changes its meaning. Establish comparable file identity before reporting alignment or mismatch, or preserve `unknown` with an explanation when the comparison cannot be established. Restart advice should correspond to an actual identity disagreement. Expected source: the [input contract](https://github.com/morluto/rea/blob/c3647ade1e557c4179a8b825a37e0c2193cd2526/src/contracts/sessionStatusContract.ts#L35) accepts an absolute local filesystem path, and [identity/discovery documentation](https://github.com/morluto/rea/blob/c3647ade1e557c4179a8b825a37e0c2193cd2526/docs/mcp-contracts.md#L904) says it compares the caller's expectation with the live session. The [caller-path owner](https://github.com/morluto/rea/blob/c3647ade1e557c4179a8b825a37e0c2193cd2526/src/filesystem/callerPath.ts) documents why POSIX symlink/`..` traversal must not be replaced by lexical normalization; the [repository invariants](https://github.com/morluto/rea/blob/c3647ade1e557c4179a8b825a37e0c2193cd2526/AGENTS.md) require preserving caller-selected paths and uncertainty when normalization loses information. ### Impact and scope P2: a valid same-file expectation produces an incorrect stale-registration result and unnecessary restart guidance. The demonstrated case is a target-free macOS Bun server launched through a directory symlink followed by `..`. No analysis failure, automatic restart, target mutation, or data loss was observed. Real-world frequency, other Bun versions, Linux, and Windows are unverified. A practical workaround was observed: use the canonical `server.command_path` returned by this live session as the expectation. The same session then reports `aligned`. ### REA version rea-agents 6.8.0; source revision `c3647ade1e557c4179a8b825a37e0c2193cd2526`. `rea --version` on the installed package returned `6.8.0`. npm reported 6.8.0 as the latest published release, and fetched origin/main matched this source revision. ### Runtime and platform macOS 27.2, arm64. Bun 1.4.4 for the server; Node.js v24.22.1 for the MCP client and focused tests. No analysis provider was needed. ### Target and artifact context No analysis target. The fixture is a symlink to the package's existing `scripts` directory and a launch spelling ending in `link/../scripts/rea.mjs`. ### Interface MCP ### Earliest failure stage Transport or result projection ### Relevant output or evidence The reduced script produced the following on both the built source checkout and the installed release: ```json {"same_file":true} {"case":"launch-spelling","alignment":{"state":"mcp_server_restart_required","reasons":["registration_path_mismatch"],"remediation":"Restart the registered MCP server/client, then compare identity again."}} {"case":"canonical-control","alignment":{"state":"aligned","reasons":[],"remediation":null}} ``` The larger real-MCP control probe also established: no expectation returns `unknown`; an unrelated absolute path returns `registration_path_mismatch`; a relative expectation is rejected; canonical Bun and Node launch/expectation pairs return `aligned`. MCP initialization succeeded, and all server processes exited zero with no stderr. The focused command `npm run test:focused -- tests/boundary/mcp/serverIdentity.test.ts src/contracts/sessionPathInputs.test.ts` passed 25 tests with 3 skips across 2 files. Those existing tests do not establish correctness for this symlink/parent fixture. ### Typed outcome or recovery expected Report `aligned` when the two filesystem selections can be established as the same entrypoint; otherwise retain `unknown` rather than a definite path mismatch based on a meaning-changing normalization. Preserve genuine mismatched-path detection. ### Analysis or suspected cause Source-derived: [the status handler](https://github.com/morluto/rea/blob/c3647ade1e557c4179a8b825a37e0c2193cd2526/src/server/registerSessionStatusTool.ts#L133) forwards the admitted expectation and current entrypoint into [createServerIdentity](https://github.com/morluto/rea/blob/c3647ade1e557c4179a8b825a37e0c2193cd2526/src/serverIdentity.ts#L49). That function compares `resolve(expected.server_path)` to the observed command-path string. In the executed fixture, the expected path's symlink/parent traversal reaches the running package, while lexical `resolve` produces a different spelling. There is no filesystem identity check or uncertainty projection in this comparison. This explains the observed mismatch; no broader runtime-path preservation claim is needed. ### Additional context Related but distinct: closed [#115](https://github.com/morluto/rea/issues/115) describes the identity/restart feature; closed [#7757](https://github.com/morluto/rea/issues/7757) and [#4899](https://github.com/morluto/rea/pull/4899) concern setup registration binding and unresolved launcher identity. Open [#5213](https://github.com/morluto/rea/pull/5213) and [#6410](https://github.com/morluto/rea/pull/6410) fix Windows identity test fixtures without changing this production comparison. Open [#6923](https://github.com/morluto/rea/pull/6923) concerns Bun compatibility and setup launcher recognition. No exact open duplicate was found in the performed issue/PR searches.
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#7774
No description provided.