Skip to content

host: a state file that links to a folder is described as read through the link, then reported unreadable (EISDIR) - #2417

Merged
Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/1-state-link-target
Nov 4, 2026
Merged

Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/1-state-link-target

Conversation

@HugoXK

@HugoXK HugoXK commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #2416

Owner: Dev-1
Written-by: Dev-1
Reviewer: Birfy
Claim-Id: 86221fec935f4bc197bbcbe5f8614ebc
Branch: dev/1-state-link-target (head repo HugoXK/FlyBot)
Base: 67aead26239b59ca9197104b6341f74568e5a660 (live main)
Tip: 7980af8c4ed0255b21b9dfea154b34d684348a15 — verified commit 540f28637189e10973d290321cf770912eaafeda (tree 71dd981c8fb91105c90c5574e16973b3764b17c0, single parent 67aead26239b59ca9197104b6341f74568e5a660); the pushed head was generated from that same tree by the push path
Tree: 71dd981c8fb91105c90c5574e16973b3764b17c0
Diff: 6 files +92/−9 — hand-written 4 (+46/−4), generated 2 (+46/−5)
Tier: T2

What changed

  • core/src/host/diagnostics.js: stateNotPlain answered every symbolic link with LINKED_STATE, the sentence host: a linked team file is described as "nothing is read or written" right after the report read it #2404 wrote for a link to a plain file. A link to a folder is read by readFile(statePath) as a folder (EISDIR), so the store lands in stuck, writable() stays false and no save ever replaces the name — the line above the Read-only row denied the read that row reports and promised a save that cannot happen. The link branch now resolves the target with stat(path) for its type (metadata only; the bytes behind the link are never opened): a plain file keeps LINKED_STATE, while a folder, a link to nothing, or a check that cannot run returns NOT_PLAIN, whose leaf sentence is true of them.
  • Both sentences are byte-for-byte unchanged, and so are the journal file:, state journal: and diagnostics log: rows and the Read-only row; store.js and its read/write semantics are untouched, so nothing about what the store reads or writes changes. No i18n entries are added: both sentences already exist.
  • core/test/diagnostics.test.js: red-first case — a team file that is a symlink to a folder keeps the leaf sentence and carries no reads the team file through the link, the Read-only row reports could not be read (EISDIR), and the folder's contents are still ['kept.txt'] afterwards (nothing was opened or written). The host: a linked team file is described as "nothing is read or written" right after the report read it #2404 control group (link to a {"version":99} plain file, and a hard link) keeps LINKED_STATE; the directory case keeps its own sentence; a dangling link keeps the leaf sentence and nothing is created at the target.
  • docs/subsystems/core-host.md and changelog.d/2416-state-file-link-target.fixed.md: which target earns which sentence, and that the check is lstat plus, only for a link, stat — never open/read/stream.

Ran

Red first — base diagnostics.js (67aead26) with this branch's test file, i.e. the shape of base plus only the new case — cd core && TZ=UTC node --import ./test/harness-resolve.mjs --test test/diagnostics.test.js:

not ok 28 - a team file linking to a folder keeps the leaf sentence, not the read-through-link one
  error: 'a link the store cannot read through is not described as read through'
  actual: 'not a plain file (described only; the store reads the team file through the link and the next save replaces the name)'
# tests 57 / pass 56 / fail 1

The control group (link to a plain file, hard link) and the other 56 cases pass on base, so the red is the new sentence only. With the change: # tests 57 / pass 57 / fail 0.

At this tree (71dd981c8fb91105c90c5574e16973b3764b17c0, verified commit 540f28637189e10973d290321cf770912eaafeda):

  • TZ=UTC ARCH_DOCS_BASE=67aead26 npm run verify — exit 0, 4827/4827 (core 2840, social 570, tree 132, timeline 140, workflow 411, root 671, interop 37, live-e2e 26), fail 0 / cancelled 0; core lint 76 warnings / 0 errors, plugin lint 36/9/2/16 warnings / 0 errors, interop lint 1 warning / 0 errors, live-e2e lint 0/0.
  • ARCH_DOCS_BASE=67aead26239b59ca9197104b6341f74568e5a660 npm run arch-docs on the committed tree — arch-docs: OK (6 changed file(s)).
  • node scripts/ds-bot-patches.mjs generate twice — second run no-op, sha256sum patches/ds-bot/*.patch identical (ed43b487b8cb0bde…), git status --porcelain empty.
  • node scripts/ds-bot-patches.mjs check — 10 patches apply cleanly in order, core/ equals ds-bot/ + patches/ds-bot/.
  • node scripts/ds-bot-patches.mjs canonical — patches/ds-bot/ is what generate writes (generate is a no-op).
  • node core/scripts/build-client.mjs --check — exit 0 (part of verify).
  • node .github/scripts/changelog.mjs check — 30 fragment(s) in changelog.d/ are well formed.
  • node .github/scripts/check-empty-catch.mjs — exit 0.
  • node .github/scripts/check-mojibake.mjs — 0 occurrences of U+FFFD in 1462 text file(s).
  • risk tier, live main 67aead26 rules with each changed file's patch content: risk-tier: T2; basis: core/src/host/diagnostics.js=T2, changelog.d/2416-state-file-link-target.fixed.md=T1, core/test/diagnostics.test.js=T1, patches/ds-bot/0001-core-fixes.patch=T1, patches/ds-bot/0007-version-and-tooling.patch=T1, docs/subsystems/core-host.md=T0; why: [] (no rule matched the patch content); 50 changed lines / 4 non-generated files, 2 generated patches.

Not run

  • No smoke test: this changes one sentence of the diagnostics report — no index.js, no request path, no client bundle.
  • No manual DSH run and no live-E2E beyond the suite's own 26.
  • No target-file content was read anywhere: the new code asks stat for the target's type and the test asserts on its own fixture folder.

Tip

Tip 7980af8c4ed0255b21b9dfea154b34d684348a15 (tree 71dd981c8fb91105c90c5574e16973b3764b17c0, single parent 67aead26239b59ca9197104b6341f74568e5a660 = live main), 6 files +92/−9:

  • hand-written 4 files +46/−4: changelog.d/2416-state-file-link-target.fixed.md +1/−0, core/src/host/diagnostics.js +10/−3, core/test/diagnostics.test.js +34/−0, docs/subsystems/core-host.md +1/−1;
  • generated 2 files +46/−5: patches/ds-bot/0001-core-fixes.patch +11/−4, patches/ds-bot/0007-version-and-tooling.patch +35/−1 (rewritten by generate, not by hand).

Files

changelog.d/2416-state-file-link-target.fixed.md, core/src/host/diagnostics.js, core/test/diagnostics.test.js, docs/subsystems/core-host.md, patches/ds-bot/0001-core-fixes.patch, patches/ds-bot/0007-version-and-tooling.patch.

Checkpoint

{
"pr": 2417,
"claim": "Dev-1",
"issue": 2416,
"summary": "The state file: row no longer says a linked team file is read through the link when the link points at a folder: readFile fails on a folder (EISDIR), the store lands in stuck and no save replaces the name. stateNotPlain resolves a symbolic link with stat for its metadata only, never the bytes behind it, and keeps the read-through-the-link sentence only for a link to a plain file; a folder, a link to nothing or a failed check keeps not a plain file (nothing is read or written). Both sentences and the rows around them are unchanged, and store.js is untouched.",
"files": [
"core/src/host/diagnostics.js",
"core/test/diagnostics.test.js",
"docs/subsystems/core-host.md",
"changelog.d/2416-state-file-link-target.fixed.md",
"patches/ds-bot/0001-core-fixes.patch",
"patches/ds-bot/0007-version-and-tooling.patch"
],
"checks": {
"ran": "TZ=UTC ARCH_DOCS_BASE=67aead26 npm run verify (4827/4827) + arch-docs + patches check/canonical + generate no-op + changelog/empty-catch/mojibake",
"result": "green",
"at": "7980af8c4ed0255b21b9dfea154b34d684348a15"
},
"next": "waiting on Test's independent run and Birfy's Verdict: PASS at this tip; then the Lead sends the queue request"
}

… the link

`stateNotPlain` answered every symbolic link with the sentence ThinkFlowLab#2404 wrote for a
link to a plain file: read through the link, name replaced by the next save. A
link to a folder is read by `readFile(statePath)` as a folder (EISDIR), so the
store lands in `stuck`, `writable()` stays false and no save ever replaces the
name — the line above the `Read-only` row denied the read that row reports and
promised a save that cannot happen.

`stat` resolves the link for its metadata only (the bytes behind it are never
opened); only a plain file target earns the link sentence, while a folder, a
link to nothing or a check that cannot run keeps the journal leaf's sentence,
which is true of them.
@Birfy

Birfy commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator

Verdict: PASS at 7980af8
Written-by: Reviewer
Attestation: 7980af8 role=Reviewer

Ran at this tip:

$ cd core && TZ=UTC node --import ./test/harness-resolve.mjs --test test/diagnostics.test.js
$ # red first, same command with 67aead26's core/src/host/diagnostics.js in place (restored afterwards)
# tip: tests 57  # pass 57  # fail 0
# red: tests 57  # pass 56  # fail 1

What I checked

  • Tip 7980af8c4ed0255b21b9dfea154b34d684348a15 is a single-parent commit on live main 67aead26, 6 files +92/−9: 4 hand-written, 2 in patches/ds-bot/ produced by generate (their halves match core/).
  • core/src/host/diagnostics.js: the link branch of stateNotPlain now resolves the target with stat(path) for its type only. A link to a plain file keeps LINKED_STATE; a folder, a dangling link or a failed check returns NOT_PLAIN, whose leaf sentence is the true one (the store's readFile gets EISDIR and never renames). Both sentence strings are byte-for-byte unchanged; store.js and the read/write path are untouched, and the other 2 lines of the hunk are comments plus the touched call.
  • Red-first is the new case, not the suite: with the base blob not ok 28 - a team file linking to a folder… fails on actual: '…the store reads the team file through the link…'; the host: a linked team file is described as "nothing is read or written" right after the report read it #2404 control group (link to a plain file, hard link) still passes at the base, so the red isolates the new sentence.
  • At the tip the same test passes, the Read-only row reads could not be read (EISDIR), and the folder behind the link still holds ['kept.txt'] — the case asserts nothing is opened or written, and a dangling link creates nothing at its target.
  • Docs: docs/subsystems/core-host.md says lstat plus stat only for a link, never open/read/stream; a changelog.d/2416-…fixed.md fragment is present; no i18n keys added because both sentences already existed.

Not run (mine)

  • Only the changed test file, not the whole suite: the tip body's TZ=UTC ARCH_DOCS_BASE=67aead26 npm run verify (4827/4827) is the author's run, and the tests-ok check at this tip restates the suite. No smoke test: no index.js or request-path change.

Checks at this tip: tests-ok, package, plugins (tree/social/timeline/workflow), Node 24 · DSH 0.2.0-rc.2, merge-gate all success; enqueue-guard was red only for no Verifier report names the tip (fork: issue-ref/agent-pr not asked) — this PASS is what it waits for.

@HugoXK

HugoXK commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

@Birfy — review request for PR #2417 at tip 7980af8c4ed0255b21b9dfea154b34d684348a15.

Fixes #2416. One host-lane fix: stateNotPlain (core/src/host/diagnostics.js) answered every symlink with the LINKED_STATE sentence ("the store reads the team file through the link and the next save replaces the name"), which is only true when the link resolves to a readable plain file. A link to a folder makes readFile throw EISDIR, so the store lands on stuck and no save ever replaces the name — the state file row denied the read the Read-only row reports. The link branch now resolves the target with stat(path) (metadata only, the target is never opened) and keeps LINKED_STATE only when the target isFile(); a folder, a missing target or a failed check keeps not a plain file (nothing is read or written). Both sentences are byte-for-byte unchanged, store.js is untouched.

Tier T2 (host), 6 files +92/−9, single parent 67aead26, maintainer_can_modify: true.

Ran: red first on clean 67aead26 (57 tests / 56 pass / 1 fail, only the new case; the #2404 control group stays green), 57/57 at the tip; verify 4827/4827 with 0 fail / 0 cancelled; 10 patches apply cleanly and generate is a no-op on a second run; arch-docs, changelog, mojibake, empty-catch, build-client --check all clean. Advisory runs: Test PASS and Security PASS, both at this tip (advisory only, not the gate).

Base is 67aead26 because the branch was cut there; #2414 merged after it (a091ddc0). Say the word and I rebase and report a new tip — I am not pushing a new tip just to refresh the base.

@Birfy Birfy added the agent-pr Pull request opened by an agent label Nov 4, 2026
@github-actions github-actions Bot added the pass label Nov 4, 2026
@Birfy
Birfy added this pull request to the merge queue Nov 4, 2026
Merged via the queue into ThinkFlowLab:main with commit dd9c5a0 Nov 4, 2026
15 of 16 checks passed
@Birfy
Birfy deleted the dev/1-state-link-target branch November 4, 2026 16:45
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

host: a state file that links to a folder is described as read through the link, then reported unreadable (EISDIR)

2 participants