Skip to content

host: the diagnostics report never says whether its own log file could be written - #2384

Merged
Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/1-diag-log-write-failure
Nov 4, 2026
Merged

Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/1-diag-log-write-failure

Conversation

@HugoXK

@HugoXK HugoXK commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #2383

Owner: Dev-1
Reviewer: Birfy
Claim-Id: ed1a3c323b00419480add1d6fd531964
Branch: dev/1-diag-log-write-failure
Tip: 442e2fe
Tree: 0abd306775432c5806481d9abc5dc466a373d57b
Base: 1848d0d
Diff: 6 files, +393/−9 (hand-written 4 files, +189/−3; generated 2 files, +204/−6)
Tier: T3
why: core/test/diagnostics.test.js matches /\.(only|skip)\(/ (the root-only t.skip of the 0444 leaf case); basis: diagnostics.js=T2, test/changelog/patches=T1, docs/subsystems/core-host.md=T0; 192 changed lines in 4 non-generated files.

What changed

The report behind Settings → Feedback's Report on GitHub never said that this host's own log file could not be written. The reason was already computed for stderr (fileFailure, refusedLeaf) and never reached facts() or reportMarkdown(), so a bundle could list recent errors while saying nothing about the file holding them.

The report now carries that one fact, in the shape the state journal: line already has (#2134): diagnostics log: recording while appends land, not written (<code>) after an append the host could not make, and not written (not a plain file of its own) when the leaf guard refused the write (#2089, so a link or another file's second name is named as the refusal it is and never as a folder). A host whose log is off (diagnosticsFile: false) adds no line at all — the answer is not "not written", there is no file to write.

The value describes the last append attempt, not the first refusal. The stderr refusal is a latch — one line per host however many entries the guard drops (#2089) — so refusedLeaf never clears. A report reading it would keep saying not written (not a plain file of its own) after the host put a plain file back and the next entry landed in it, or after the leaf became a plain 0444 file whose append fails for a different reason; either way it would describe a file the leaf no longer is. leafRefusedNow is cleared when an attempt starts and set again by a refusal, fileFailure is cleared by the append that lands, and the report reads both. So a leaf that is still a link reads the guard's sentence, a plain 0444 leaf reads not written (EACCES) even after an earlier refusal, and a leaf that landed reads recording; the latch keeps its single stderr line.

Unchanged, and the point of the slice: clear()'s three states (absent / kept / truncated) and #2364's guarantee that a real truncate failure is named on exactly one [ds-bot] diagnostics log not truncated: EACCES stderr line. core/src/client/feedback.js, core/client.js and patches/ds-bot/0008 are untouched — the client's sentences are another change's surface.

Docs: docs/subsystems/core-host.md, at the end of the diagnostics-log bullet, gets the sentence that says the report names whether that same file is being written, with the three values, the no-line case, and that the value follows the last append attempt while the guard's stderr line stays one per host, linked to this issue.

Tests — red first

On the base with only core/test/diagnostics.test.js from this branch: 44 tests / 41 pass / 3 fail / 0 cancelled. not ok 14, 15 and 16 each diff + undefined against the expected not written (EACCES), not written (not a plain file of its own) and recording; the runner itself printed [ds-bot] diagnostics log is not a plain file of its own; nothing was written there on stderr while the report carried nothing.

The first precision case has its own red at the tree before its commit, with only that case taken: 1 test / 0 pass / 1 fail. The assertion is report.facts.diagnosticsLog: + 'not written (not a plain file of its own)' against - 'recording', while the leaf already held the one line that landed in it and the file behind the old link kept its bytes.

The second precision case has the same shape: a link refused, then the leaf replaced by a 0444 plain file whose append fails, red at the tree before this commit with only that case taken: 1 test / 0 pass / 1 fail. The assertion is report.facts.diagnosticsLog: + 'not written (not a plain file of its own)' against - 'not written (EACCES)'.

After the fixes: core/test/diagnostics.test.js → 46 tests / 46 pass / 0 fail / 0 cancelled.

Ran

  • TZ=UTC ARCH_DOCS_BASE=1848d0df npm run verify → green; 4794 tests (2807 core + 570 social + 132 tree + 140 timeline + 411 workflow + 671 root scripts/policy + 37 interop + 26 live-e2e), 0 fail, 0 cancelled; arch-docs: OK (6 changed file(s))
  • 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 scripts/ds-bot-patches.mjs generate twice → exit 0, identical patch hashes, clean worktree
  • node core/scripts/build-client.mjs --check → exit 0
  • node .github/scripts/changelog.mjs check → 20 fragment(s) … well formed
  • node .github/scripts/check-empty-catch.mjs → exit 0
  • node .github/scripts/check-mojibake.mjs --check → exit 0
  • cd core && npm run lint → exit 0

Not run

  • the smoke test (this change touches no index.js and no src/host/ wiring beyond one module's own facts)
  • the CI package job (pack check, typecheck, actionlint) and the merge queue — no queue action was taken.

Checkpoint

{
"pr": 2384, "claim": "Dev-1", "issue": 2383,
"summary": "The diagnostics report now says whether FlyBot's own error log is being written: facts() carries diagnosticsLog and the markdown prints a diagnostics log line in the state journal's shape — recording, not written (), or not written (not a plain file of its own); a log-off host adds no line. The value describes the last append attempt: a landed append reads recording, a plain 0444 leaf reads EACCES even after an earlier refusal, and a link reads the guard sentence. The one-line stderr refusal stays a per-host latch (#2089); clear()'s three states and #2364 are untouched.",
"files": ["core/src/host/diagnostics.js", "core/test/diagnostics.test.js", "docs/subsystems/core-host.md", "changelog.d/2383-diag-log-write-failure.fixed.md", "patches/ds-bot/0001-core-fixes.patch", "patches/ds-bot/0007-version-and-tooling.patch"],
"checks": { "ran": "TZ=UTC ARCH_DOCS_BASE=1848d0df npm run verify", "result": "green", "at": "442e2feb14d9ca9a77dca467e9129b71723e82bb" },
"next": "none — ready for review"
}

@HugoXK
HugoXK force-pushed the dev/1-diag-log-write-failure branch from 28d8e64 to d38a93f Compare November 4, 2026 10:58
`leafRefusedNow` was cleared only when an append landed, so the failure
branch left it as the first refusal had set it: a host whose linked leaf
became a plain `0444` file met `EACCES` on the next append while the report
still said `not written (not a plain file of its own)` -- a claim about a
file the leaf no longer is.

The flag now starts each attempt false and `refuseLeaf()` sets it again, so
the line describes the last attempt: a `0444` leaf reads `not written
(EACCES)`, a leaf that lands reads `recording`, and a leaf that is a link
reads the guard's sentence. `refusedLeaf` and its one stderr line per host
are untouched (ThinkFlowLab#2089), and `clear()` keeps its three answers (ThinkFlowLab#2364).
@HugoXK
HugoXK force-pushed the dev/1-diag-log-write-failure branch from d38a93f to 442e2fe Compare November 4, 2026 11:13
@HugoXK

HugoXK commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

@Birfy — ready for a Verdict on this tip: 442e2feb14d9ca9a77dca467e9129b71723e82bb (T3).

diagnostics log: is one new line in the report, so the exact-tip review is about three things holding together:

docs/subsystems/core-host.md and changelog.d/2383-diag-log-write-failure.fixed.md were changed in this same PR, and core/src/client/** / core/client.js / patches/ds-bot/0008 have zero diff against the base.

Please answer with Verdict: PASS naming 442e2feb14d9ca9a77dca467e9129b71723e82bb or the list of things to fix. Checks at this tip: 14 success + 1 failure, and the one failure is enqueue-guard waiting for exactly this Verdict.

@Birfy

Birfy commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator

Verdict: PASS at 442e2fe
Written-by: Reviewer
Attestation: 442e2fe role=Reviewer

Ran at this tip:

$ cd core && TZ=UTC node --import ./test/harness-resolve.mjs --test test/diagnostics.test.js
# tests 46  # pass 46  # fail 0
$ cp <base:1848d0df57c580d12a1038eb29fc0a300231f668>/core/src/host/diagnostics.js core/src/host/diagnostics.js
$ cd core && TZ=UTC node --import ./test/harness-resolve.mjs --test test/diagnostics.test.js
# tests 46  # pass 41  # fail 5
$ cp <tip:442e2feb14d9ca9a77dca467e9129b71723e82bb>/core/src/host/diagnostics.js core/src/host/diagnostics.js
$ cd core && TZ=UTC node --import ./test/harness-resolve.mjs --test test/diagnostics.test.js
# tests 46  # pass 46  # fail 0

CI at this head. 14 success + 1 failure on 442e2feb — tests-ok success at 2026-11-04T11:15:30Z (its test, plugins (tree|social|timeline|workflow), package, ubuntu-latest · Node 24 · DSH 0.2.0-rc.2 all success), merge-gate success, reviewed tip (report only) success, risk tier (report only) success. The one failure is enqueue-guard: do not enqueue #2384: no Verifier report names the tip 442e2feb14d9, which this PASS is what clears, and it says in the same run that the head lives in a fork so the Fixes/agent-pr rules are not asked of it. Tier is T3 per the author's basis (a root-only t.skip in core/test/diagnostics.test.js matches the tier rule).

Red first. With core/src/host/diagnostics.js put back to the base blob and the new test file unchanged, 5 of 46 go red — the EACCES case, the leaf-guard case, recording after the leaf becomes writable again, "the reason the last append met", and the on/off pair all read + undefined against their expected diagnostics log: fact, because facts.diagnosticsLog and the markdown line do not exist at the base. Restoring the tip blob returns 46/46.

What I read. facts() adds diagnosticsLog in the journal line's shape: undefined when diagnosticsFile === false, not written (not a plain file of its own) when leafRefusedNow, not written (<code>) from fileFailure, otherwise recording; reportMarkdown() appends it as its own paragraph and nothing else moves. The two latches stay latches — refusedLeaf and fileFailed keep their one stderr line per host — while leafRefusedNow is cleared when an attempt starts and fileFailure is cleared by the append that lands, so the report describes the last attempt and not the first refusal; a plain 0444 leaf after an earlier link refusal reads EACCES, and the file behind the link keeps its bytes. clear()'s absent|kept|truncated, rotated, and #2364's single truncate EACCES line are untouched. Docs (docs/subsystems/core-host.md, same paragraph, linked to #2383) and changelog.d/2383-diag-log-write-failure.fixed.md are in the same diff; core/src/client/**, core/client.js and patches/ds-bot/0008 have zero diff, and the two changed patches/ds-bot files are the regenerated mirror of core/, not hand edits. 6 files, all in scope. The checkpoint block is well formed (1132 characters, summary 582, 6 files, checks.at this tip).

No conclusion named this tip before this one.

@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 460738c Nov 4, 2026
15 checks passed
@Birfy
Birfy deleted the dev/1-diag-log-write-failure branch November 4, 2026 11:41
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: the diagnostics report never says whether its own log file could be written

2 participants