Repository navigation
Description
Version
LifeOS 8.761.0 · attestation: not run
What is broken
Three defects in the Report tool's contribution path on 8.761.0. They're filed together because each one sits on the route from file to pr.
1. pr can't apply any patch to the release repo. diffAllowed accepts only install-relative paths (LIFEOS/..., skills/..., hooks/...) and refuses the LifeOS/install/-prefixed form. Meanwhile pr clones danielmiessler/LifeOS, where the install tree lives under LifeOS/install/, and runs git apply --index at the clone root. A patch that passes the gate therefore matches no file in the clone, and every run stops at "patch does not apply". The staged-path check has the same problem in reverse: it would read the prefixed paths that diffAllowed refuses. The tools already know this layout: Attest.ts:112 describes the release payload as <tag>/LifeOS/install/{hooks,LIFEOS,...}, and the stamped install.sh defaults LIFEOS_RELEASE_SUBPATH to LifeOS. Only pr misses it. The Report intent's gotcha (Intents/Report.md) then blames the failure on the user: "If git apply fails, the fix was made against a modified install; regenerate the diff against the release". A user who does exactly that gets the same error. As far as I can find, the only two PRs ever opened through Report.ts pr are mine (#2464, #2484), and both were opened with the fix below already in place.
2. A PR sent after file reads as a second copy of the issue. After file, the tool says "State kept for the pull request: run verify --cmd if not done, then pr". pr then posts the same report.md as the PR body with no reference to the issue, which leaves two near-identical threads and no link from the fix to the report.
3. The attestation line puts the modified-file list under the missing count. attestationLine adds · N system file(s) missing and only then the : that introduces the list. Since the list is a.modified, it reads as the missing files. On my install the Version field says "299 system file(s) missing:" above hooks/ISASync.hook.ts, which exists and differs. Anyone triaging a report reads that as a broken install, which is why I've left the attestation out of this one.
Where (file:line)
skills/LifeOS/Tools/Report.ts:670, Report.ts:681, Attest.ts:176
Repro on a clean tree
# 1. pr's apply step, on the clone pr itself makes
d=$(mktemp -d)
git clone -q --depth 1 https://github.com/danielmiessler/LifeOS.git $d/r
cd $d/r/LifeOS/install && echo x >> LIFEOS/VERSION && git diff --relative > $d/p && git checkout -q .
cd $d/r && git apply --index $d/p # error: LIFEOS/VERSION: does not exist in index
# 2. after `Report.ts file --yes`, run `Report.ts pr --diff <patch> --branch <b>` (dry run):
# step 5 posts --body-file report.md, the issue's own text, with no reference to the issue
# 3. from a v8.761.0 install root
bun -e 'import {attestationLine} from "./skills/LifeOS/Tools/Attest.ts"; console.log(attestationLine({status: "modified", reference: "r", compared: 3, modified: ["hooks/ISASync.hook.ts"], missing: 299}))'
# attestation: 1 system file(s) differ from r · 299 system file(s) missing:
# - hooks/ISASync.hook.tsNegative control
Probe: d=$(mktemp -d); git clone -q --depth 1 https://github.com/danielmiessler/LifeOS.git $d/r; cd $d/r/LifeOS/install && echo x >> LIFEOS/VERSION && git diff --relative > $d/p && git checkout -q . && cd $d/r; echo "--- attestationLine (a modified list plus a missing count):"; bun -e "import {attestationLine} from \"$RELEASE_TREE/skills/LifeOS/Tools/Attest.ts\"; console.log(attestationLine({status: \"modified\", reference: \"danielmiessler/LifeOS@v8.761.0\", compared: 3, modified: [\"hooks/ISASync.hook.ts\"], missing: 299}))"; echo "--- the install-relative patch pr sends:"; cat $d/p; echo "--- git apply --index at the clone root, as pr runs it:"; git apply --index $d/p · exit 1 · captured 2026-10-30T20:26:24.472Z
Red looks like: 299 system file(s) missing:
--- attestationLine (a modified list plus a missing count):
attestation: 1 system file(s) differ from danielmiessler/LifeOS@v8.761.0 · 299 system file(s) missing:
- hooks/ISASync.hook.ts
--- the install-relative patch pr sends:
diff --git a/LIFEOS/VERSION b/LIFEOS/VERSION
index ac17eee..78ee549 100644
--- a/LIFEOS/VERSION
+++ b/LIFEOS/VERSION
@@ -1 +1,2 @@
8.761.0
+x
--- git apply --index at the clone root, as pr runs it:
--- stderr ---
error: LIFEOS/VERSION: does not exist in index
Suggested fix
The attached diff touches three files under skills/LifeOS/Tools/:
Report.tsgetsapplyAtInstallRoot. It applies the patch with--directory=<install root>when the clone keeps the install tree underLifeOS/install/(falling back to the root otherwise), then returns the staged paths relative to that root, sostagedDeniedjudges the same pathsdiffAlloweddid.prBodyForends the PR body withFixes #<n>when the report was already filed as an issue on the same repo, and the PR body goes out from a temp file, soreport.mdstays as filed. The dry-run plan names both steps.Attest.tslabels the list:· 299 system file(s) missing\ndiffering files:\n - ….Report.test.tsis new, with 5 cases: apply on the release layout and on a root-layout repo,prBodyForlinking and not linking, and the attestation label. It can't load on v8.761.0, becauseapplyAtInstallRootdoesn't exist there. The negative control above runs the defects directly instead.
"Verified after fix" repeats the same clone and patch against a v8.761.0 install tree with this diff applied ($RELEASE_TREE), and runs the tests. The fix has also been working in practice: #2464 and #2484 were opened through it, and the #2484 dry run listed no issue-create step and a body ending in Fixes #2483.
diff --git a/skills/LifeOS/Tools/Attest.ts b/skills/LifeOS/Tools/Attest.ts
index fb78ecc..d948fb1 100644
--- a/skills/LifeOS/Tools/Attest.ts
+++ b/skills/LifeOS/Tools/Attest.ts
@@ -173,7 +173,9 @@ export function attestationLine(a: Attestation | null): string {
const list = a.modified.slice(0, 40).map((f) => ` - ${f}`).join("\n");
const more = a.modified.length > 40 ? `\n - …and ${a.modified.length - 40} more` : "";
const missing = a.missing ? ` · ${a.missing} system file(s) missing` : "";
- return `attestation: ${a.modified.length} system file(s) differ from ${a.reference}${missing}${a.modified.length ? ":\n" + list + more : ""}`;
+ // The list is the modified files, so it is labelled as such: after the missing count, a bare colon read as
+ // though the list named the missing files.
+ return `attestation: ${a.modified.length} system file(s) differ from ${a.reference}${missing}${a.modified.length ? "\ndiffering files:\n" + list + more : ""}`;
}
return `attestation: unattested (${a.reason})`;
}
diff --git a/skills/LifeOS/Tools/Report.test.ts b/skills/LifeOS/Tools/Report.test.ts
new file mode 100644
index 0000000..56c489a
--- /dev/null
+++ b/skills/LifeOS/Tools/Report.test.ts
@@ -0,0 +1,77 @@
+import { describe, expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
+import { join } from "node:path";
+import { tmpdir } from "node:os";
+import { spawnSync } from "node:child_process";
+import { applyAtInstallRoot, prBodyFor } from "./Report";
+import { attestationLine } from "./Attest";
+
+const git = (cwd: string, ...args: string[]) =>
+ spawnSync("git", ["-c", "user.name=t", "-c", "user.email=t@localhost", ...args], { cwd, encoding: "utf8" });
+
+// An install-relative patch, the shape diffAllowed accepts and `pr --diff` sends.
+const PATCH = [
+ "diff --git a/LIFEOS/TOOLS/a.ts b/LIFEOS/TOOLS/a.ts",
+ "--- a/LIFEOS/TOOLS/a.ts",
+ "+++ b/LIFEOS/TOOLS/a.ts",
+ "@@ -1 +1 @@",
+ "-export const a = 1;",
+ "+export const a = 2;",
+ "",
+].join("\n");
+
+/** A one-commit repo whose install tree sits at `root` (the release repo uses LifeOS/install). */
+function repoWithInstallAt(root: string): { dir: string; patch: string } {
+ const dir = mkdtempSync(join(tmpdir(), "report-apply-"));
+ mkdirSync(join(dir, root, "LIFEOS", "TOOLS"), { recursive: true });
+ writeFileSync(join(dir, root, "LIFEOS", "TOOLS", "a.ts"), "export const a = 1;\n");
+ git(dir, "init", "-q");
+ git(dir, "add", "-A");
+ git(dir, "commit", "-qm", "init");
+ const patch = join(dir, "..", `${dir.split("/").pop()}.patch`);
+ writeFileSync(patch, PATCH);
+ return { dir, patch };
+}
+
+describe("pr applies an install-relative patch", () => {
+ test("to the release repo layout (install tree under LifeOS/install/)", () => {
+ const { dir, patch } = repoWithInstallAt("LifeOS/install");
+ // What v8.761.0 runs: git apply at the repo root, which matches no file in this layout.
+ expect(git(dir, "apply", "--check", patch).status).not.toBe(0);
+ const r = applyAtInstallRoot(dir, patch);
+ expect(r.ok).toBe(true);
+ expect(r.prefix).toBe("LifeOS/install");
+ // Staged paths come back install-relative, so stagedDenied judges the same paths the diff gate did.
+ expect(r.stagedZ).toBe("LIFEOS/TOOLS/a.ts");
+ });
+
+ test("to a repo whose root is the install tree", () => {
+ const { dir, patch } = repoWithInstallAt("");
+ const r = applyAtInstallRoot(dir, patch);
+ expect(r.ok).toBe(true);
+ expect(r.prefix).toBe("");
+ expect(r.stagedZ).toBe("LIFEOS/TOOLS/a.ts");
+ });
+});
+
+describe("prBodyFor", () => {
+ test("links the issue the report was filed as on the same repo", () => {
+ const body = prBodyFor("report\n", "o/r", "https://github.com/o/r/issues/12");
+ expect(body.endsWith("\n\nFixes #12\n")).toBe(true);
+ });
+
+ test("leaves the body alone when nothing was filed there", () => {
+ expect(prBodyFor("report\n", "o/r", undefined)).toBe("report\n");
+ expect(prBodyFor("report\n", "o/r", "https://github.com/x/y/issues/12")).toBe("report\n");
+ expect(prBodyFor("report\n", "o/r", "https://github.com/o/r/discussions/12")).toBe("report\n");
+ });
+});
+
+describe("attestationLine", () => {
+ test("labels the modified list, never the missing count", () => {
+ const line = attestationLine({ status: "modified", reference: "o/r@v1", compared: 3, modified: ["hooks/A.ts"], missing: 2 });
+ expect(line).not.toContain("missing:");
+ expect(line).toContain("2 system file(s) missing");
+ expect(line).toContain("differing files:\n - hooks/A.ts");
+ });
+});
diff --git a/skills/LifeOS/Tools/Report.ts b/skills/LifeOS/Tools/Report.ts
index 1b3dfde..e591ea4 100644
--- a/skills/LifeOS/Tools/Report.ts
+++ b/skills/LifeOS/Tools/Report.ts
@@ -492,6 +492,30 @@ export function resolvePrArgs(dir: string, patch: string | undefined, branch: st
return { patch: p, branch: b };
}
+/** Repo-relative homes of the install tree, nearest first. The release repo keeps it under LifeOS/install/. */
+const INSTALL_ROOTS = ["", "LifeOS/install"];
+
+/**
+ * A contribution carries install-relative paths (diffAllowed), but the release repo keeps the install tree below
+ * its root, so applying at the root never matches a file. Applies the patch at the install root the clone has and
+ * returns the staged paths relative to that root, so the staged-path check reads the same paths the gate did.
+ */
+export function applyAtInstallRoot(repoDir: string, patchFile: string): { ok: boolean; err: string; prefix: string; stagedZ: string } {
+ const prefix = INSTALL_ROOTS.find((d) => existsSync(join(repoDir, d, "LIFEOS"))) ?? "";
+ const r = git(repoDir, ["apply", "--index", ...(prefix ? [`--directory=${prefix}`] : []), patchFile]);
+ if (!r.ok) return { ok: false, err: r.err, prefix, stagedZ: "" };
+ const staged = git(repoDir, ["diff", "--cached", "--name-only", "--no-renames", "-z"]);
+ if (!staged.ok) return { ok: false, err: staged.err || "could not list the staged paths", prefix, stagedZ: "" };
+ const strip = (p: string) => (prefix && p.startsWith(`${prefix}/`) ? p.slice(prefix.length + 1) : p);
+ return { ok: true, err: "", prefix, stagedZ: staged.out.split("\0").filter(Boolean).map(strip).join("\0") };
+}
+
+/** The PR body links the issue the report was already filed as on the same repo, so it reads as its fix. */
+export function prBodyFor(report: string, upstream: string, filed: string | undefined): string {
+ const n = filed?.startsWith(`https://github.com/${upstream}/issues/`) ? filed.split("/").pop() : undefined;
+ return n && /^\d+$/.test(n) ? `${report.trimEnd()}\n\nFixes #${n}\n` : report;
+}
+
async function main(): Promise<void> {
const a = process.argv.slice(2);
const cmd = a[0];
@@ -642,18 +666,20 @@ async function main(): Promise<void> {
const repoName = upstream.split("/")[1];
const fork = `${login}/${repoName}`;
const base = gh(["repo", "view", upstream, "--json", "defaultBranchRef", "-q", ".defaultBranchRef.name"]).out || "main";
+ const prBody = prBodyFor(readFileSync(reportPath, "utf8"), upstream, built.filed);
+ const linked = prBody.match(/\nFixes #(\d+)\n$/)?.[1];
const plan = [
`1. gh repo fork ${upstream} --clone=false (no-op if ${fork} exists)`,
`2. git clone --depth 1 -b ${base} https://github.com/${upstream}.git <tmp>`,
- `3. git checkout -b ${branch} && git apply --index <diff> (${gate.paths.length} file(s): ${gate.paths.join(", ")})`,
+ `3. git checkout -b ${branch} && git apply --index [--directory=<install root>] <diff> (${gate.paths.length} file(s): ${gate.paths.join(", ")})`,
`4. git commit && git push https://github.com/${fork}.git ${branch} (auth: gh credential helper, per-command)`,
- `5. gh pr create -R ${upstream} --base ${base} --head ${login}:${branch} --title "${built.title}" --body-file ${reportPath}`,
+ `5. gh pr create -R ${upstream} --base ${base} --head ${login}:${branch} --title "${built.title}" --body-file <tmp>/pr-body.md (report.md${linked ? ` + "Fixes #${linked}"` : ""}, ${prBody.length} chars)`,
].join("\n");
if (!yes) { console.log(`DRY RUN — nothing pushed. \`--yes\` runs:\n${plan}`); return; }
// The push makes the patch public on the fork before the PR exists, so the patch and the PR text are proofed
// here, before any fork or push; gh pr create proofs the PR text again at its own call.
- const pre = await prePushGate(built.title, readFileSync(reportPath, "utf8"), patch);
+ const pre = await prePushGate(built.title, prBody, patch);
if (pre) die(pre);
const f = gh(["repo", "fork", upstream, "--clone=false"], 120_000);
@@ -667,18 +693,19 @@ async function main(): Promise<void> {
let r = git(work, ["clone", "--depth", "1", "-b", base, `https://github.com/${upstream}.git`, repoDir]);
if (!r.ok) die(r.err || "clone failed");
r = git(repoDir, ["checkout", "-b", branch]); if (!r.ok) die(r.err);
- r = git(repoDir, ["apply", "--index", patchFile]); if (!r.ok) die(`patch does not apply to ${upstream}@${base}: ${r.err}`);
+ const applied = applyAtInstallRoot(repoDir, patchFile);
+ if (!applied.ok) die(`patch does not apply to ${upstream}@${base}: ${applied.err}`);
// The gate read the patch text; this reads what git actually staged, so no path is saved unchecked.
- const staged = git(repoDir, ["diff", "--cached", "--name-only", "--no-renames", "-z"]);
- if (!staged.ok) die(staged.err || "could not list the staged paths");
- const stagedBad = stagedDenied(staged.out);
+ const stagedBad = stagedDenied(applied.stagedZ);
if (stagedBad.length) die(`Applying the diff staged paths a contribution may never carry: ${stagedBad.join(", ")}`, 2);
const ident = ["-c", `user.name=${login}`, "-c", `user.email=${login}@users.noreply.github.com`];
r = git(repoDir, [...ident, "commit", "-m", `${built.title}\n\nFiled from a LifeOS install with the Report tool; the negative control and the green run are in the PR body.`]);
if (!r.ok) die(r.err || "commit failed");
r = git(repoDir, ["-c", "credential.helper=!gh auth git-credential", "push", `https://github.com/${fork}.git`, `HEAD:refs/heads/${branch}`]);
if (!r.ok) die(r.err || "push failed");
- const prSent = await gatedPublish(["pr", "create", "-R", upstream, "--base", base, "--head", `${login}:${branch}`, "--title", built.title, "--body-file", reportPath], built.title, readFileSync(reportPath, "utf8"), {}, 120_000);
+ const bodyFile = join(work, "pr-body.md");
+ writeFileSync(bodyFile, prBody);
+ const prSent = await gatedPublish(["pr", "create", "-R", upstream, "--base", base, "--head", `${login}:${branch}`, "--title", built.title, "--body-file", bodyFile], built.title, prBody, {}, 120_000);
if ("blocked" in prSent) die(prSent.blocked);
const pr = prSent.r;
if (!pr.ok) die(pr.err || "gh pr create failed");Verified after fix
Probe: d=$(mktemp -d); git clone -q --depth 1 https://github.com/danielmiessler/LifeOS.git $d/r; cd $d/r/LifeOS/install && echo x >> LIFEOS/VERSION && git diff --relative > $d/p && git checkout -q . && cd $d/r; echo "--- attestationLine (a modified list plus a missing count):"; bun -e "import {attestationLine} from \"$RELEASE_TREE/skills/LifeOS/Tools/Attest.ts\"; console.log(attestationLine({status: \"modified\", reference: \"danielmiessler/LifeOS@v8.761.0\", compared: 3, modified: [\"hooks/ISASync.hook.ts\"], missing: 299}))"; echo "--- applyAtInstallRoot on the same clone and patch:"; bun -e "import {applyAtInstallRoot} from \"$RELEASE_TREE/skills/LifeOS/Tools/Report.ts\"; const r = applyAtInstallRoot(\"$d/r\", \"$d/p\"); console.log(JSON.stringify({ok: r.ok, prefix: r.prefix, staged: r.stagedZ})); process.exit(r.ok ? 0 : 1)" && git status --short && echo "--- Report.test.ts:" && cd $RELEASE_TREE && bun test skills/LifeOS/Tools/Report.test.ts 2>&1 | tail -4 · exit 0 · captured 2026-10-30T20:27:43.831Z
--- attestationLine (a modified list plus a missing count):
attestation: 1 system file(s) differ from danielmiessler/LifeOS@v8.761.0 · 299 system file(s) missing
differing files:
- hooks/ISASync.hook.ts
--- applyAtInstallRoot on the same clone and patch:
{"ok":true,"prefix":"LifeOS/install","staged":"LIFEOS/VERSION"}
M LifeOS/install/LIFEOS/VERSION
--- Report.test.ts:
5 pass
0 fail
14 expect() calls
Ran 5 tests across 1 file. [101.00ms]
Environment
- OS: Pop!_OS 24.04 LTS (x64)
- Harness: claude-code
- bun: 1.3.14 · git: 2.56.0
- Doctor broken: none
Activity