docs(agents): guard history dating against a shallow clone - #14808

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-14793-shallow-clone-history-guard
Sep 3, 2026
Merged

docs(agents): guard history dating against a shallow clone#14808
os-zhuang merged 1 commit into
mainfrom
claude/issue-14793-shallow-clone-history-guard

Conversation

@os-litant

@os-litantos-litant commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Fixes#14793

One standing clause added to .claude/agents/os-dev.md, paid same-file at ratchet headroom 0 (469 lines in, 469 lines out). Head sha for every reading below: c6d554a.

The line added — verbatim, at line 232

- **`log -S/--follow/blame` 判日期或先后前**,先 `git rev-parse --is-shallow-repository`,true 就加深申报。

119 UTF-8 bytes, under the corpus cap of 120.

It is phrased for history reads only. A content read at a ref (git show ref:path, git grep ... ref --) reads the tree at the tip, which a shallow clone does not truncate, so it is depth-safe and is deliberately not caught by the wording: the trigger is the three history verbs plus the two things you can get wrong from them, a date or an ordering.

Placement: the bullet immediately after the git family rule in the standing clauses. That neighbour is the file's other clause about a git command whose answer depends on repository state the reader did not check (there, shared refs and the stash; here, depth).

The line paid — verbatim, was line 303, in Definition of done

- 用户可见的改动加 changeset。

Why its removal is justified on its own merits

It is a strict restatement. Basic rule 4, lines 71-72, states the same obligation on the same subject, more precisely, at a more binding site:

4. **永不**编辑 `content/docs/releases/`、force-push、推 `main`、合并任何东西。用户可见
的改动需要 `.changeset/*.md`。

Same subject phrase, 用户可见的改动; the surviving spelling names the file glob (.changeset/*.md) where the deleted one said only "changeset". The surviving site is one of the six basic rules — the part of the file the contract itself treats as the rules that must never be missed. grep -c '用户可见' over the file was 2 before this PR and is 1 after: the fact is not lost, only its weaker copy. (The phrase 用户可见的改动 itself now greps to 0 because basic rule 4 wraps it across lines 71-72 — a line-scoped grep is exactly the wrong instrument for judging whether this fact survives, which is worth a reviewer's second look.)

The Definition of done also keeps a changeset step of its own: the skip-changeset bullet still makes the dev decide changeset-versus-label against a stated criterion (publishes nothing from any package), so a change that does not qualify for the label is routed straight back to basic rule 4.

Chosen over the two other restatements considered because its omission fails loudly: a missing changeset is a red Check Changeset gate on the PR, caught by CI within the same round.

Candidates rejected, and why

  1. 资源纪律 rule 6's ⛔ 永不把验证挂在后台 watcher 上然后停轮(禁令与两种合法终态见「干净收尾」)。 — also a self-admitted narrow copy of a rule whose home is 干净收尾 clause 4, and legal under the file's own "one rule, one home" convention. Rejected: it is a forward pointer across a long distance, so it carries real navigational value, and its omission fails silently (a stalled round nobody is woken for).
  2. The Definition of done's last bullet, 拆掉你启动的一切 —— dev server,以及你挂起的每一个后台 monitor(见下节)。 — restated as a principle by 干净收尾 clause 1, which sits directly below it. Rejected for the same asymmetry: a leaked monitor replays a whole report at the PM and nothing goes red.
  3. Re-wrapping the file's degenerate single-token wrap lines — 7 of them, at lines 21, 23, 65, 69, 397, 405 and 447 (, 期 +, , , ——, 怎么, 义),直). Refused outright: the maintainer's 2026-08-17 ruling makes deleting content the only legal currency for the line ratchet, and the file carries that ruling itself. (They remain a legitimate independent density repair under the 2026-08-29 ruling — worth up to about 7 lines of headroom on a file pinned at 469/469 — but that is a separate PR that buys no content, not a payment bundled with this one.)

A costing correction the card should carry

The card and the triage both cost this as "one line", from a prose sentence of about 200 bytes. Under the corpus's 120-byte line cap, the ruled content does not fit on one line: naming the probe command alone costs 39 bytes, the three verbs 23, and a complete spelling with the failure evidence measures 185-190 bytes, i.e. two lines and therefore two payments.

Rather than take a second deletion, the wording was compressed to fit one line, and two things were dropped to do it:

  • the evidence clause (a shallow clone answers with the horizon commit, exit 0, plausible sha, plausible date, no warning) — it lives in the card and in the transcript below;
  • the deepen spelling (git fetch --unshallow, or --deepen=N until the answer stops moving) — the line says 加深, which is the same verb the file already uses for this at line 158.

Naming that trade rather than silently making the line longer, because it is the maintainer's call whether the evidence is worth a second payment.

Reverse verification of the guard's claim

Throwaway clone, made and deleted inside this run; never one of the shared checkouts. Cloned over file:// from the container's own mirror so --depth is honoured and no network is used.

$ git clone --depth 50 --branch main file:///home/user/objectstack /home/user/os-14793-shallow-probe
$ git -C ... rev-parse --is-shallow-repository
true
$ git -C ... rev-list --count HEAD
50
$ git log -S'Pure bug fixes do' --format='%h %ad %s' --date=short -- AGENTS.md
b72db01 2026-08-26 fix(spec,core): PluginHealthMonitor stops claiming a restart it never performed ... (#12589)
log exit=0
# that sha is the clone's own boundary commit, not an answer about AGENTS.md:
$ git log --format='%h %ad %s' --date=short -1 $(git rev-list --max-parents=0 HEAD | head -1)
b72db01 2026-08-26 fix(spec,core): PluginHealthMonitor stops claiming a restart it never performed ...
$ git fetch --unshallow
$ git rev-parse --is-shallow-repository
false
$ git rev-list --count HEAD
11462
$ git log -S'Pure bug fixes do' --format='%h %ad %s' --date=short -- AGENTS.md
43625fbd6 2026-05-30 docs: update agent instructions and launch configs
log exit=0

88 days of error, same exit code, no warning, and the wrong answer is a real commit with a real date. The full-history checkout this PR was written in independently reports 43625fbd6c 2026-05-30, matching the card's own measurement.

The framing correction, measured again here

The card's reassurance that the shared checkout "is no longer shallow" was a property of one container. Measured in this one, before any edit:

/home/user/objectstack : is-shallow-repository = false (12142 commits)
/home/user/objectui : is-shallow-repository = true (50 commits)

Two mirrors, one container, opposite answers. Neither "the checkout is deep now" nor "every container is shallow" is a durable fact about a given tree, which is the argument for a guard that says check, per repo, at the moment you are about to date something rather than one that says deepen at startup.

The pm-dispatch site, named and not added

Measured rather than assumed, and the answer inverts the card's evidence. The card's "0 occurrences of shallow" is a grep for the ASCII word; the corpus spells it in Chinese. .claude/skills/pm-dispatch/** already carries the rule in two places:

  • references/platform-readings.md, in 读数五坑: 浅检出上的历史读数不可信 naming merge-base --is-ancestor, rev-list --count and branch -r --contains, with the remedy 先 --deepen 再判,或走 REST compare.
  • SKILL.md's tool table row for scripts/pm/git-history.mjs: 窗口化 commit 计数:回答或 REFUSE —— 浅 clone 对窗口化 git log/rev-list 以 exit 0 无警告答错, plus historyHorizon() as a read-only predicate for self-answering tools.

So the PM half is not a gap and no second ratchet needs paying. What was uncovered was the os-dev half and the specific verb set: neither site names -S, --follow or blame, which are the dating verbs, and os-dev.md's single prior mention of a shallow checkout (line 157) is about dispatch-gates.mjsrefusing loudly — the opposite signal from the silent one this clause guards.

One consistency note for the reviewer, since it looks like a contradiction and is not. scripts/pm/git-history.mjs records that --is-shallow-repository is the wrong predicate on its own: after a legitimate deepen a repo can still report true while answering the asked window exactly, so a guard that refused on it would refuse correct answers. This clause does not refuse on it — it deepens on it. For a windowed count you can prove coverage (the floor sits below the window) and refusing is avoidable; for a -S dating read there is no window to prove anything against, so deepen until the answer stops moving is the only sound rule. The same file's other finding is why the clause says 加深 and never --shallow-since: that flag deepens or shortens, exit 0, no warning.

Gates, all at c6d554a — 11 commands, all green, nothing left unmeasured

Union re-derived after the final commit: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (stderr confirms the answer is about this repo at this commit) returns 9 commands. All 9 were run, plus the 2 the dispatch named that the derivation does not carry.

commandexitverdict
pnpm check:pm-skill-ratchet0✓ check-skill-line-ratchet: .claude/agents/os-dev.md is 469 lines (ceiling 469; headroom 0). and ✓ ... widest table row is 0 bytes (pin 0; headroom 0). and ✓ check-skill-line-ratchet self-test: 111 cases pass. — identical verdicts before and after the edit
pnpm check:pm-governed-prose0✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces
pnpm check:pm-skill-id-lint0✓ check-skill-id-lint: 23 file(s) clean (pattern /#[0-9]{3,}/g).
pnpm check:skill-frame-sync0✓ check-skill-frame-sync: 4 copies of the decision frame are structurally isomorphic across 3 files
pnpm check:agent-model-declared0✓ check-agent-model-declared: 1 agent definition(s) under .claude/agents/ all declare a model
pnpm check:nul-bytes0check-nul-bytes: OK (scanned 8064 text file(s) ... no raw ASCII control bytes).
pnpm check:corpus-claim-drift0check-corpus-claim-drift: OK, no new claim sites beside a pinned spelling.
pnpm check:agent-test-spelling0✓ check-agent-test-spelling: 0 violations — 430 file(s)
pnpm check:doc-authoring0✓ doc authoring guard: sibling-package prose ids hold the baseline — 831 pinned site(s) across 231 file(s)
pnpm check:pm-governed-merges0✓ check-governed-merges --self-test: 243 assertions
pnpm --filter @objectstack/lint run check:doc-formula-expressions0✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 426 files / 1365 TS blocks judged clean by @objectstack/formula. — the first two attempts exited 3, PREREQUISITE NOT MET, read as NOT MEASURED rather than red; it became measurable only after building @objectstack/spec, @objectstack/formula and @objectstack/lint under the verify lock. This gate reads .claude as one of its four ROOTS, so it genuinely covers the edited file and was worth paying for.

Every exit code was captured by redirecting to a file before any pipe, and each verdict above is the gate's own printed line, never a bare $?.

Edit-landed-on-disk proof, independent of any tool's exit code:

readingbeforeafter
wc -l .claude/agents/os-dev.md469469
wc -c .claude/agents/os-dev.md4274042820
grep -c 'is-shallow-repository'01
grep -c of the deleted line, anchored10
git diff --stat1 file changed, 1 insertion(+), 1 deletion(-)

Control bytes: grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' over the edited file exits 1 (no match), beside the gate.

eslint, narrowed — with the three pieces

Not "not run": measured, and the narrowing is proved not to have excluded anything.

  1. Population read from eslint's own config, not from a guess. Every files: entry in eslint.config.mjs (lines 785, 971, 1015, 1054, 1103, 1172, 1212) is an extension glob over {ts,tsx,mts,cts,js,jsx,mjs,cjs}. A case-insensitive grep of the config for markdown, processor and the .md extension returns 0 hits — there is no markdown processor, so a .md file is not in the linted population at all.
  2. File count read from --format json.npx eslint --no-inline-config --format json .claude/agents/os-dev.md exits 0 and returns 1 result with errorCount: 0 and one message: File ignored because no matching configuration was supplied. The changed-file count inside the lint population is 0 of 1.
  3. Invariance for untouched files.grep -c projectService eslint.config.mjs is 0, and the config states it itself at line 328: no parserOptions.project, no typed @typescript-eslint rules. With no type-aware linting there is no cross-file verdict coupling, so a markdown file outside the population cannot move any untouched file's result.

Landing

.claude/agents/os-dev.md line 418 is held by PR #14779 (one line, in the decision-frame region). This hunk is at lines 232 and 301 and is disjoint from it; whichever lands later merges main in rather than rebasing, and both survive.

No changeset: this publishes nothing from any package, so the skip-changeset label is applied instead. Applied by union write (documentation, size/xs, skip-changeset) after reading the existing set, since this session has no additive REST endpoint; comparative read-back showed all three present, and a delayed re-read is owed because an immediate read-back cannot detect a later strip.

Draft, and it stays draft — governed .claude/**, human merge is the review record. Not flipped ready, not enqueued, no reviewers requested.


🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1

`git log -S`, `git log --follow` and `git blame` answer from the horizon
commit in a shallow clone — exit 0, plausible sha, plausible date, no
warning — so a dev asked to date a line of history can report a wrong
answer with no signal. One standing clause now requires
`git rev-parse --is-shallow-repository` before a date or an ordering is
trusted, deepening when it is true, and declaring it in the report.
Scoped to history reads only. A content read at a ref (`git show
ref:path`, `git grep … ref --`) is depth-safe and is deliberately not
caught by the wording.
Paid same-file at ratchet headroom 0: the Definition-of-done line
"用户可见的改动加 changeset。" is dropped as a strict restatement of basic
rule 4, which states the same obligation more precisely
("用户可见的改动需要 `.changeset/*.md`") at a more binding site.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@os-zhuang
os-zhuang marked this pull request as ready for review September 3, 2026 15:16
@os-zhuang
os-zhuang added this pull request to the merge queueSep 3, 2026
@hotlongClaude

Copy link
Copy Markdown
Contributor

Maintainer ruling, 2026-09-03, given in chat to the director seat (session_01WXyGTWPbbreqXow7Z2pZCk) on decision batch 2, item 1 (this PR, option A: merge as-is). Verbatim: 「同意」.

Executing on that instruction from the maintainer's own account: marking ready, bringing the branch up to date with main by a merge commit so CI re-validates the ratchet against today's tree (the PR's last CI run predates the merges since 03:00Z), then merging directly (squash) once the head is green. No approving review is submitted by any seat; the human merge is the review record for the governed path .claude/agents/os-dev.md.


Generated by Claude Code

Merged via the queue into main with commit 3386493Sep 3, 2026
36 of 37 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-14793-shallow-clone-history-guard branch September 3, 2026 15:44
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/xsskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

4 participants

@os-litant@hotlong@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

docs(agents): guard history dating against a shallow clone - #14808

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-14793-shallow-clone-history-guard
Sep 3, 2026
Merged

docs(agents): guard history dating against a shallow clone#14808
os-zhuang merged 1 commit into
mainfrom
claude/issue-14793-shallow-clone-history-guard

Conversation

@os-litant

@os-litantos-litant commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Fixes#14793

One standing clause added to .claude/agents/os-dev.md, paid same-file at ratchet headroom 0 (469 lines in, 469 lines out). Head sha for every reading below: c6d554a.

The line added — verbatim, at line 232

- **`log -S/--follow/blame` 判日期或先后前**,先 `git rev-parse --is-shallow-repository`,true 就加深申报。

119 UTF-8 bytes, under the corpus cap of 120.

It is phrased for history reads only. A content read at a ref (git show ref:path, git grep ... ref --) reads the tree at the tip, which a shallow clone does not truncate, so it is depth-safe and is deliberately not caught by the wording: the trigger is the three history verbs plus the two things you can get wrong from them, a date or an ordering.

Placement: the bullet immediately after the git family rule in the standing clauses. That neighbour is the file's other clause about a git command whose answer depends on repository state the reader did not check (there, shared refs and the stash; here, depth).

The line paid — verbatim, was line 303, in Definition of done

- 用户可见的改动加 changeset。

Why its removal is justified on its own merits

It is a strict restatement. Basic rule 4, lines 71-72, states the same obligation on the same subject, more precisely, at a more binding site:

4. **永不**编辑 `content/docs/releases/`、force-push、推 `main`、合并任何东西。用户可见
的改动需要 `.changeset/*.md`。

Same subject phrase, 用户可见的改动; the surviving spelling names the file glob (.changeset/*.md) where the deleted one said only "changeset". The surviving site is one of the six basic rules — the part of the file the contract itself treats as the rules that must never be missed. grep -c '用户可见' over the file was 2 before this PR and is 1 after: the fact is not lost, only its weaker copy. (The phrase 用户可见的改动 itself now greps to 0 because basic rule 4 wraps it across lines 71-72 — a line-scoped grep is exactly the wrong instrument for judging whether this fact survives, which is worth a reviewer's second look.)

The Definition of done also keeps a changeset step of its own: the skip-changeset bullet still makes the dev decide changeset-versus-label against a stated criterion (publishes nothing from any package), so a change that does not qualify for the label is routed straight back to basic rule 4.

Chosen over the two other restatements considered because its omission fails loudly: a missing changeset is a red Check Changeset gate on the PR, caught by CI within the same round.

Candidates rejected, and why

  1. 资源纪律 rule 6's ⛔ 永不把验证挂在后台 watcher 上然后停轮(禁令与两种合法终态见「干净收尾」)。 — also a self-admitted narrow copy of a rule whose home is 干净收尾 clause 4, and legal under the file's own "one rule, one home" convention. Rejected: it is a forward pointer across a long distance, so it carries real navigational value, and its omission fails silently (a stalled round nobody is woken for).
  2. The Definition of done's last bullet, 拆掉你启动的一切 —— dev server,以及你挂起的每一个后台 monitor(见下节)。 — restated as a principle by 干净收尾 clause 1, which sits directly below it. Rejected for the same asymmetry: a leaked monitor replays a whole report at the PM and nothing goes red.
  3. Re-wrapping the file's degenerate single-token wrap lines — 7 of them, at lines 21, 23, 65, 69, 397, 405 and 447 (, 期 +, , , ——, 怎么, 义),直). Refused outright: the maintainer's 2026-08-17 ruling makes deleting content the only legal currency for the line ratchet, and the file carries that ruling itself. (They remain a legitimate independent density repair under the 2026-08-29 ruling — worth up to about 7 lines of headroom on a file pinned at 469/469 — but that is a separate PR that buys no content, not a payment bundled with this one.)

A costing correction the card should carry

The card and the triage both cost this as "one line", from a prose sentence of about 200 bytes. Under the corpus's 120-byte line cap, the ruled content does not fit on one line: naming the probe command alone costs 39 bytes, the three verbs 23, and a complete spelling with the failure evidence measures 185-190 bytes, i.e. two lines and therefore two payments.

Rather than take a second deletion, the wording was compressed to fit one line, and two things were dropped to do it:

  • the evidence clause (a shallow clone answers with the horizon commit, exit 0, plausible sha, plausible date, no warning) — it lives in the card and in the transcript below;
  • the deepen spelling (git fetch --unshallow, or --deepen=N until the answer stops moving) — the line says 加深, which is the same verb the file already uses for this at line 158.

Naming that trade rather than silently making the line longer, because it is the maintainer's call whether the evidence is worth a second payment.

Reverse verification of the guard's claim

Throwaway clone, made and deleted inside this run; never one of the shared checkouts. Cloned over file:// from the container's own mirror so --depth is honoured and no network is used.

$ git clone --depth 50 --branch main file:///home/user/objectstack /home/user/os-14793-shallow-probe
$ git -C ... rev-parse --is-shallow-repository
true
$ git -C ... rev-list --count HEAD
50
$ git log -S'Pure bug fixes do' --format='%h %ad %s' --date=short -- AGENTS.md
b72db01 2026-08-26 fix(spec,core): PluginHealthMonitor stops claiming a restart it never performed ... (#12589)
log exit=0
# that sha is the clone's own boundary commit, not an answer about AGENTS.md:
$ git log --format='%h %ad %s' --date=short -1 $(git rev-list --max-parents=0 HEAD | head -1)
b72db01 2026-08-26 fix(spec,core): PluginHealthMonitor stops claiming a restart it never performed ...
$ git fetch --unshallow
$ git rev-parse --is-shallow-repository
false
$ git rev-list --count HEAD
11462
$ git log -S'Pure bug fixes do' --format='%h %ad %s' --date=short -- AGENTS.md
43625fbd6 2026-05-30 docs: update agent instructions and launch configs
log exit=0

88 days of error, same exit code, no warning, and the wrong answer is a real commit with a real date. The full-history checkout this PR was written in independently reports 43625fbd6c 2026-05-30, matching the card's own measurement.

The framing correction, measured again here

The card's reassurance that the shared checkout "is no longer shallow" was a property of one container. Measured in this one, before any edit:

/home/user/objectstack : is-shallow-repository = false (12142 commits)
/home/user/objectui : is-shallow-repository = true (50 commits)

Two mirrors, one container, opposite answers. Neither "the checkout is deep now" nor "every container is shallow" is a durable fact about a given tree, which is the argument for a guard that says check, per repo, at the moment you are about to date something rather than one that says deepen at startup.

The pm-dispatch site, named and not added

Measured rather than assumed, and the answer inverts the card's evidence. The card's "0 occurrences of shallow" is a grep for the ASCII word; the corpus spells it in Chinese. .claude/skills/pm-dispatch/** already carries the rule in two places:

  • references/platform-readings.md, in 读数五坑: 浅检出上的历史读数不可信 naming merge-base --is-ancestor, rev-list --count and branch -r --contains, with the remedy 先 --deepen 再判,或走 REST compare.
  • SKILL.md's tool table row for scripts/pm/git-history.mjs: 窗口化 commit 计数:回答或 REFUSE —— 浅 clone 对窗口化 git log/rev-list 以 exit 0 无警告答错, plus historyHorizon() as a read-only predicate for self-answering tools.

So the PM half is not a gap and no second ratchet needs paying. What was uncovered was the os-dev half and the specific verb set: neither site names -S, --follow or blame, which are the dating verbs, and os-dev.md's single prior mention of a shallow checkout (line 157) is about dispatch-gates.mjsrefusing loudly — the opposite signal from the silent one this clause guards.

One consistency note for the reviewer, since it looks like a contradiction and is not. scripts/pm/git-history.mjs records that --is-shallow-repository is the wrong predicate on its own: after a legitimate deepen a repo can still report true while answering the asked window exactly, so a guard that refused on it would refuse correct answers. This clause does not refuse on it — it deepens on it. For a windowed count you can prove coverage (the floor sits below the window) and refusing is avoidable; for a -S dating read there is no window to prove anything against, so deepen until the answer stops moving is the only sound rule. The same file's other finding is why the clause says 加深 and never --shallow-since: that flag deepens or shortens, exit 0, no warning.

Gates, all at c6d554a — 11 commands, all green, nothing left unmeasured

Union re-derived after the final commit: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (stderr confirms the answer is about this repo at this commit) returns 9 commands. All 9 were run, plus the 2 the dispatch named that the derivation does not carry.

commandexitverdict
pnpm check:pm-skill-ratchet0✓ check-skill-line-ratchet: .claude/agents/os-dev.md is 469 lines (ceiling 469; headroom 0). and ✓ ... widest table row is 0 bytes (pin 0; headroom 0). and ✓ check-skill-line-ratchet self-test: 111 cases pass. — identical verdicts before and after the edit
pnpm check:pm-governed-prose0✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces
pnpm check:pm-skill-id-lint0✓ check-skill-id-lint: 23 file(s) clean (pattern /#[0-9]{3,}/g).
pnpm check:skill-frame-sync0✓ check-skill-frame-sync: 4 copies of the decision frame are structurally isomorphic across 3 files
pnpm check:agent-model-declared0✓ check-agent-model-declared: 1 agent definition(s) under .claude/agents/ all declare a model
pnpm check:nul-bytes0check-nul-bytes: OK (scanned 8064 text file(s) ... no raw ASCII control bytes).
pnpm check:corpus-claim-drift0check-corpus-claim-drift: OK, no new claim sites beside a pinned spelling.
pnpm check:agent-test-spelling0✓ check-agent-test-spelling: 0 violations — 430 file(s)
pnpm check:doc-authoring0✓ doc authoring guard: sibling-package prose ids hold the baseline — 831 pinned site(s) across 231 file(s)
pnpm check:pm-governed-merges0✓ check-governed-merges --self-test: 243 assertions
pnpm --filter @objectstack/lint run check:doc-formula-expressions0✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 426 files / 1365 TS blocks judged clean by @objectstack/formula. — the first two attempts exited 3, PREREQUISITE NOT MET, read as NOT MEASURED rather than red; it became measurable only after building @objectstack/spec, @objectstack/formula and @objectstack/lint under the verify lock. This gate reads .claude as one of its four ROOTS, so it genuinely covers the edited file and was worth paying for.

Every exit code was captured by redirecting to a file before any pipe, and each verdict above is the gate's own printed line, never a bare $?.

Edit-landed-on-disk proof, independent of any tool's exit code:

readingbeforeafter
wc -l .claude/agents/os-dev.md469469
wc -c .claude/agents/os-dev.md4274042820
grep -c 'is-shallow-repository'01
grep -c of the deleted line, anchored10
git diff --stat1 file changed, 1 insertion(+), 1 deletion(-)

Control bytes: grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' over the edited file exits 1 (no match), beside the gate.

eslint, narrowed — with the three pieces

Not "not run": measured, and the narrowing is proved not to have excluded anything.

  1. Population read from eslint's own config, not from a guess. Every files: entry in eslint.config.mjs (lines 785, 971, 1015, 1054, 1103, 1172, 1212) is an extension glob over {ts,tsx,mts,cts,js,jsx,mjs,cjs}. A case-insensitive grep of the config for markdown, processor and the .md extension returns 0 hits — there is no markdown processor, so a .md file is not in the linted population at all.
  2. File count read from --format json.npx eslint --no-inline-config --format json .claude/agents/os-dev.md exits 0 and returns 1 result with errorCount: 0 and one message: File ignored because no matching configuration was supplied. The changed-file count inside the lint population is 0 of 1.
  3. Invariance for untouched files.grep -c projectService eslint.config.mjs is 0, and the config states it itself at line 328: no parserOptions.project, no typed @typescript-eslint rules. With no type-aware linting there is no cross-file verdict coupling, so a markdown file outside the population cannot move any untouched file's result.

Landing

.claude/agents/os-dev.md line 418 is held by PR #14779 (one line, in the decision-frame region). This hunk is at lines 232 and 301 and is disjoint from it; whichever lands later merges main in rather than rebasing, and both survive.

No changeset: this publishes nothing from any package, so the skip-changeset label is applied instead. Applied by union write (documentation, size/xs, skip-changeset) after reading the existing set, since this session has no additive REST endpoint; comparative read-back showed all three present, and a delayed re-read is owed because an immediate read-back cannot detect a later strip.

Draft, and it stays draft — governed .claude/**, human merge is the review record. Not flipped ready, not enqueued, no reviewers requested.


🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1

`git log -S`, `git log --follow` and `git blame` answer from the horizon
commit in a shallow clone — exit 0, plausible sha, plausible date, no
warning — so a dev asked to date a line of history can report a wrong
answer with no signal. One standing clause now requires
`git rev-parse --is-shallow-repository` before a date or an ordering is
trusted, deepening when it is true, and declaring it in the report.
Scoped to history reads only. A content read at a ref (`git show
ref:path`, `git grep … ref --`) is depth-safe and is deliberately not
caught by the wording.
Paid same-file at ratchet headroom 0: the Definition-of-done line
"用户可见的改动加 changeset。" is dropped as a strict restatement of basic
rule 4, which states the same obligation more precisely
("用户可见的改动需要 `.changeset/*.md`") at a more binding site.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@os-zhuang
os-zhuang marked this pull request as ready for review September 3, 2026 15:16
@os-zhuang
os-zhuang added this pull request to the merge queueSep 3, 2026
@hotlongClaude

Copy link
Copy Markdown
Contributor

Maintainer ruling, 2026-09-03, given in chat to the director seat (session_01WXyGTWPbbreqXow7Z2pZCk) on decision batch 2, item 1 (this PR, option A: merge as-is). Verbatim: 「同意」.

Executing on that instruction from the maintainer's own account: marking ready, bringing the branch up to date with main by a merge commit so CI re-validates the ratchet against today's tree (the PR's last CI run predates the merges since 03:00Z), then merging directly (squash) once the head is green. No approving review is submitted by any seat; the human merge is the review record for the governed path .claude/agents/os-dev.md.


Generated by Claude Code

Merged via the queue into main with commit 3386493Sep 3, 2026
36 of 37 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-14793-shallow-clone-history-guard branch September 3, 2026 15:44
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/xsskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

4 participants

@os-litant@hotlong@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(agents): guard history dating against a shallow clone - #14808

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-14793-shallow-clone-history-guard
Sep 3, 2026
Merged

docs(agents): guard history dating against a shallow clone#14808
os-zhuang merged 1 commit into
mainfrom
claude/issue-14793-shallow-clone-history-guard

Conversation

@os-litant

@os-litantos-litant commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Fixes#14793

One standing clause added to .claude/agents/os-dev.md, paid same-file at ratchet headroom 0 (469 lines in, 469 lines out). Head sha for every reading below: c6d554a.

The line added — verbatim, at line 232

- **`log -S/--follow/blame` 判日期或先后前**,先 `git rev-parse --is-shallow-repository`,true 就加深申报。

119 UTF-8 bytes, under the corpus cap of 120.

It is phrased for history reads only. A content read at a ref (git show ref:path, git grep ... ref --) reads the tree at the tip, which a shallow clone does not truncate, so it is depth-safe and is deliberately not caught by the wording: the trigger is the three history verbs plus the two things you can get wrong from them, a date or an ordering.

Placement: the bullet immediately after the git family rule in the standing clauses. That neighbour is the file's other clause about a git command whose answer depends on repository state the reader did not check (there, shared refs and the stash; here, depth).

The line paid — verbatim, was line 303, in Definition of done

- 用户可见的改动加 changeset。

Why its removal is justified on its own merits

It is a strict restatement. Basic rule 4, lines 71-72, states the same obligation on the same subject, more precisely, at a more binding site:

4. **永不**编辑 `content/docs/releases/`、force-push、推 `main`、合并任何东西。用户可见
的改动需要 `.changeset/*.md`。

Same subject phrase, 用户可见的改动; the surviving spelling names the file glob (.changeset/*.md) where the deleted one said only "changeset". The surviving site is one of the six basic rules — the part of the file the contract itself treats as the rules that must never be missed. grep -c '用户可见' over the file was 2 before this PR and is 1 after: the fact is not lost, only its weaker copy. (The phrase 用户可见的改动 itself now greps to 0 because basic rule 4 wraps it across lines 71-72 — a line-scoped grep is exactly the wrong instrument for judging whether this fact survives, which is worth a reviewer's second look.)

The Definition of done also keeps a changeset step of its own: the skip-changeset bullet still makes the dev decide changeset-versus-label against a stated criterion (publishes nothing from any package), so a change that does not qualify for the label is routed straight back to basic rule 4.

Chosen over the two other restatements considered because its omission fails loudly: a missing changeset is a red Check Changeset gate on the PR, caught by CI within the same round.

Candidates rejected, and why

  1. 资源纪律 rule 6's ⛔ 永不把验证挂在后台 watcher 上然后停轮(禁令与两种合法终态见「干净收尾」)。 — also a self-admitted narrow copy of a rule whose home is 干净收尾 clause 4, and legal under the file's own "one rule, one home" convention. Rejected: it is a forward pointer across a long distance, so it carries real navigational value, and its omission fails silently (a stalled round nobody is woken for).
  2. The Definition of done's last bullet, 拆掉你启动的一切 —— dev server,以及你挂起的每一个后台 monitor(见下节)。 — restated as a principle by 干净收尾 clause 1, which sits directly below it. Rejected for the same asymmetry: a leaked monitor replays a whole report at the PM and nothing goes red.
  3. Re-wrapping the file's degenerate single-token wrap lines — 7 of them, at lines 21, 23, 65, 69, 397, 405 and 447 (, 期 +, , , ——, 怎么, 义),直). Refused outright: the maintainer's 2026-08-17 ruling makes deleting content the only legal currency for the line ratchet, and the file carries that ruling itself. (They remain a legitimate independent density repair under the 2026-08-29 ruling — worth up to about 7 lines of headroom on a file pinned at 469/469 — but that is a separate PR that buys no content, not a payment bundled with this one.)

A costing correction the card should carry

The card and the triage both cost this as "one line", from a prose sentence of about 200 bytes. Under the corpus's 120-byte line cap, the ruled content does not fit on one line: naming the probe command alone costs 39 bytes, the three verbs 23, and a complete spelling with the failure evidence measures 185-190 bytes, i.e. two lines and therefore two payments.

Rather than take a second deletion, the wording was compressed to fit one line, and two things were dropped to do it:

  • the evidence clause (a shallow clone answers with the horizon commit, exit 0, plausible sha, plausible date, no warning) — it lives in the card and in the transcript below;
  • the deepen spelling (git fetch --unshallow, or --deepen=N until the answer stops moving) — the line says 加深, which is the same verb the file already uses for this at line 158.

Naming that trade rather than silently making the line longer, because it is the maintainer's call whether the evidence is worth a second payment.

Reverse verification of the guard's claim

Throwaway clone, made and deleted inside this run; never one of the shared checkouts. Cloned over file:// from the container's own mirror so --depth is honoured and no network is used.

$ git clone --depth 50 --branch main file:///home/user/objectstack /home/user/os-14793-shallow-probe
$ git -C ... rev-parse --is-shallow-repository
true
$ git -C ... rev-list --count HEAD
50
$ git log -S'Pure bug fixes do' --format='%h %ad %s' --date=short -- AGENTS.md
b72db01 2026-08-26 fix(spec,core): PluginHealthMonitor stops claiming a restart it never performed ... (#12589)
log exit=0
# that sha is the clone's own boundary commit, not an answer about AGENTS.md:
$ git log --format='%h %ad %s' --date=short -1 $(git rev-list --max-parents=0 HEAD | head -1)
b72db01 2026-08-26 fix(spec,core): PluginHealthMonitor stops claiming a restart it never performed ...
$ git fetch --unshallow
$ git rev-parse --is-shallow-repository
false
$ git rev-list --count HEAD
11462
$ git log -S'Pure bug fixes do' --format='%h %ad %s' --date=short -- AGENTS.md
43625fbd6 2026-05-30 docs: update agent instructions and launch configs
log exit=0

88 days of error, same exit code, no warning, and the wrong answer is a real commit with a real date. The full-history checkout this PR was written in independently reports 43625fbd6c 2026-05-30, matching the card's own measurement.

The framing correction, measured again here

The card's reassurance that the shared checkout "is no longer shallow" was a property of one container. Measured in this one, before any edit:

/home/user/objectstack : is-shallow-repository = false (12142 commits)
/home/user/objectui : is-shallow-repository = true (50 commits)

Two mirrors, one container, opposite answers. Neither "the checkout is deep now" nor "every container is shallow" is a durable fact about a given tree, which is the argument for a guard that says check, per repo, at the moment you are about to date something rather than one that says deepen at startup.

The pm-dispatch site, named and not added

Measured rather than assumed, and the answer inverts the card's evidence. The card's "0 occurrences of shallow" is a grep for the ASCII word; the corpus spells it in Chinese. .claude/skills/pm-dispatch/** already carries the rule in two places:

  • references/platform-readings.md, in 读数五坑: 浅检出上的历史读数不可信 naming merge-base --is-ancestor, rev-list --count and branch -r --contains, with the remedy 先 --deepen 再判,或走 REST compare.
  • SKILL.md's tool table row for scripts/pm/git-history.mjs: 窗口化 commit 计数:回答或 REFUSE —— 浅 clone 对窗口化 git log/rev-list 以 exit 0 无警告答错, plus historyHorizon() as a read-only predicate for self-answering tools.

So the PM half is not a gap and no second ratchet needs paying. What was uncovered was the os-dev half and the specific verb set: neither site names -S, --follow or blame, which are the dating verbs, and os-dev.md's single prior mention of a shallow checkout (line 157) is about dispatch-gates.mjsrefusing loudly — the opposite signal from the silent one this clause guards.

One consistency note for the reviewer, since it looks like a contradiction and is not. scripts/pm/git-history.mjs records that --is-shallow-repository is the wrong predicate on its own: after a legitimate deepen a repo can still report true while answering the asked window exactly, so a guard that refused on it would refuse correct answers. This clause does not refuse on it — it deepens on it. For a windowed count you can prove coverage (the floor sits below the window) and refusing is avoidable; for a -S dating read there is no window to prove anything against, so deepen until the answer stops moving is the only sound rule. The same file's other finding is why the clause says 加深 and never --shallow-since: that flag deepens or shortens, exit 0, no warning.

Gates, all at c6d554a — 11 commands, all green, nothing left unmeasured

Union re-derived after the final commit: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (stderr confirms the answer is about this repo at this commit) returns 9 commands. All 9 were run, plus the 2 the dispatch named that the derivation does not carry.

commandexitverdict
pnpm check:pm-skill-ratchet0✓ check-skill-line-ratchet: .claude/agents/os-dev.md is 469 lines (ceiling 469; headroom 0). and ✓ ... widest table row is 0 bytes (pin 0; headroom 0). and ✓ check-skill-line-ratchet self-test: 111 cases pass. — identical verdicts before and after the edit
pnpm check:pm-governed-prose0✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces
pnpm check:pm-skill-id-lint0✓ check-skill-id-lint: 23 file(s) clean (pattern /#[0-9]{3,}/g).
pnpm check:skill-frame-sync0✓ check-skill-frame-sync: 4 copies of the decision frame are structurally isomorphic across 3 files
pnpm check:agent-model-declared0✓ check-agent-model-declared: 1 agent definition(s) under .claude/agents/ all declare a model
pnpm check:nul-bytes0check-nul-bytes: OK (scanned 8064 text file(s) ... no raw ASCII control bytes).
pnpm check:corpus-claim-drift0check-corpus-claim-drift: OK, no new claim sites beside a pinned spelling.
pnpm check:agent-test-spelling0✓ check-agent-test-spelling: 0 violations — 430 file(s)
pnpm check:doc-authoring0✓ doc authoring guard: sibling-package prose ids hold the baseline — 831 pinned site(s) across 231 file(s)
pnpm check:pm-governed-merges0✓ check-governed-merges --self-test: 243 assertions
pnpm --filter @objectstack/lint run check:doc-formula-expressions0✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 426 files / 1365 TS blocks judged clean by @objectstack/formula. — the first two attempts exited 3, PREREQUISITE NOT MET, read as NOT MEASURED rather than red; it became measurable only after building @objectstack/spec, @objectstack/formula and @objectstack/lint under the verify lock. This gate reads .claude as one of its four ROOTS, so it genuinely covers the edited file and was worth paying for.

Every exit code was captured by redirecting to a file before any pipe, and each verdict above is the gate's own printed line, never a bare $?.

Edit-landed-on-disk proof, independent of any tool's exit code:

readingbeforeafter
wc -l .claude/agents/os-dev.md469469
wc -c .claude/agents/os-dev.md4274042820
grep -c 'is-shallow-repository'01
grep -c of the deleted line, anchored10
git diff --stat1 file changed, 1 insertion(+), 1 deletion(-)

Control bytes: grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' over the edited file exits 1 (no match), beside the gate.

eslint, narrowed — with the three pieces

Not "not run": measured, and the narrowing is proved not to have excluded anything.

  1. Population read from eslint's own config, not from a guess. Every files: entry in eslint.config.mjs (lines 785, 971, 1015, 1054, 1103, 1172, 1212) is an extension glob over {ts,tsx,mts,cts,js,jsx,mjs,cjs}. A case-insensitive grep of the config for markdown, processor and the .md extension returns 0 hits — there is no markdown processor, so a .md file is not in the linted population at all.
  2. File count read from --format json.npx eslint --no-inline-config --format json .claude/agents/os-dev.md exits 0 and returns 1 result with errorCount: 0 and one message: File ignored because no matching configuration was supplied. The changed-file count inside the lint population is 0 of 1.
  3. Invariance for untouched files.grep -c projectService eslint.config.mjs is 0, and the config states it itself at line 328: no parserOptions.project, no typed @typescript-eslint rules. With no type-aware linting there is no cross-file verdict coupling, so a markdown file outside the population cannot move any untouched file's result.

Landing

.claude/agents/os-dev.md line 418 is held by PR #14779 (one line, in the decision-frame region). This hunk is at lines 232 and 301 and is disjoint from it; whichever lands later merges main in rather than rebasing, and both survive.

No changeset: this publishes nothing from any package, so the skip-changeset label is applied instead. Applied by union write (documentation, size/xs, skip-changeset) after reading the existing set, since this session has no additive REST endpoint; comparative read-back showed all three present, and a delayed re-read is owed because an immediate read-back cannot detect a later strip.

Draft, and it stays draft — governed .claude/**, human merge is the review record. Not flipped ready, not enqueued, no reviewers requested.


🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1

`git log -S`, `git log --follow` and `git blame` answer from the horizon
commit in a shallow clone — exit 0, plausible sha, plausible date, no
warning — so a dev asked to date a line of history can report a wrong
answer with no signal. One standing clause now requires
`git rev-parse --is-shallow-repository` before a date or an ordering is
trusted, deepening when it is true, and declaring it in the report.
Scoped to history reads only. A content read at a ref (`git show
ref:path`, `git grep … ref --`) is depth-safe and is deliberately not
caught by the wording.
Paid same-file at ratchet headroom 0: the Definition-of-done line
"用户可见的改动加 changeset。" is dropped as a strict restatement of basic
rule 4, which states the same obligation more precisely
("用户可见的改动需要 `.changeset/*.md`") at a more binding site.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@os-zhuang
os-zhuang marked this pull request as ready for review September 3, 2026 15:16
@os-zhuang
os-zhuang added this pull request to the merge queueSep 3, 2026
@hotlongClaude

Copy link
Copy Markdown
Contributor

Maintainer ruling, 2026-09-03, given in chat to the director seat (session_01WXyGTWPbbreqXow7Z2pZCk) on decision batch 2, item 1 (this PR, option A: merge as-is). Verbatim: 「同意」.

Executing on that instruction from the maintainer's own account: marking ready, bringing the branch up to date with main by a merge commit so CI re-validates the ratchet against today's tree (the PR's last CI run predates the merges since 03:00Z), then merging directly (squash) once the head is green. No approving review is submitted by any seat; the human merge is the review record for the governed path .claude/agents/os-dev.md.


Generated by Claude Code

Merged via the queue into main with commit 3386493Sep 3, 2026
36 of 37 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-14793-shallow-clone-history-guard branch September 3, 2026 15:44
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/xsskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

4 participants

@os-litant@hotlong@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(agents): guard history dating against a shallow clone - #14808

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-14793-shallow-clone-history-guard
Sep 3, 2026
Merged

docs(agents): guard history dating against a shallow clone#14808
os-zhuang merged 1 commit into
mainfrom
claude/issue-14793-shallow-clone-history-guard

Conversation

@os-litant

@os-litantos-litant commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Fixes#14793

One standing clause added to .claude/agents/os-dev.md, paid same-file at ratchet headroom 0 (469 lines in, 469 lines out). Head sha for every reading below: c6d554a.

The line added — verbatim, at line 232

- **`log -S/--follow/blame` 判日期或先后前**,先 `git rev-parse --is-shallow-repository`,true 就加深申报。

119 UTF-8 bytes, under the corpus cap of 120.

It is phrased for history reads only. A content read at a ref (git show ref:path, git grep ... ref --) reads the tree at the tip, which a shallow clone does not truncate, so it is depth-safe and is deliberately not caught by the wording: the trigger is the three history verbs plus the two things you can get wrong from them, a date or an ordering.

Placement: the bullet immediately after the git family rule in the standing clauses. That neighbour is the file's other clause about a git command whose answer depends on repository state the reader did not check (there, shared refs and the stash; here, depth).

The line paid — verbatim, was line 303, in Definition of done

- 用户可见的改动加 changeset。

Why its removal is justified on its own merits

It is a strict restatement. Basic rule 4, lines 71-72, states the same obligation on the same subject, more precisely, at a more binding site:

4. **永不**编辑 `content/docs/releases/`、force-push、推 `main`、合并任何东西。用户可见
的改动需要 `.changeset/*.md`。

Same subject phrase, 用户可见的改动; the surviving spelling names the file glob (.changeset/*.md) where the deleted one said only "changeset". The surviving site is one of the six basic rules — the part of the file the contract itself treats as the rules that must never be missed. grep -c '用户可见' over the file was 2 before this PR and is 1 after: the fact is not lost, only its weaker copy. (The phrase 用户可见的改动 itself now greps to 0 because basic rule 4 wraps it across lines 71-72 — a line-scoped grep is exactly the wrong instrument for judging whether this fact survives, which is worth a reviewer's second look.)

The Definition of done also keeps a changeset step of its own: the skip-changeset bullet still makes the dev decide changeset-versus-label against a stated criterion (publishes nothing from any package), so a change that does not qualify for the label is routed straight back to basic rule 4.

Chosen over the two other restatements considered because its omission fails loudly: a missing changeset is a red Check Changeset gate on the PR, caught by CI within the same round.

Candidates rejected, and why

  1. 资源纪律 rule 6's ⛔ 永不把验证挂在后台 watcher 上然后停轮(禁令与两种合法终态见「干净收尾」)。 — also a self-admitted narrow copy of a rule whose home is 干净收尾 clause 4, and legal under the file's own "one rule, one home" convention. Rejected: it is a forward pointer across a long distance, so it carries real navigational value, and its omission fails silently (a stalled round nobody is woken for).
  2. The Definition of done's last bullet, 拆掉你启动的一切 —— dev server,以及你挂起的每一个后台 monitor(见下节)。 — restated as a principle by 干净收尾 clause 1, which sits directly below it. Rejected for the same asymmetry: a leaked monitor replays a whole report at the PM and nothing goes red.
  3. Re-wrapping the file's degenerate single-token wrap lines — 7 of them, at lines 21, 23, 65, 69, 397, 405 and 447 (, 期 +, , , ——, 怎么, 义),直). Refused outright: the maintainer's 2026-08-17 ruling makes deleting content the only legal currency for the line ratchet, and the file carries that ruling itself. (They remain a legitimate independent density repair under the 2026-08-29 ruling — worth up to about 7 lines of headroom on a file pinned at 469/469 — but that is a separate PR that buys no content, not a payment bundled with this one.)

A costing correction the card should carry

The card and the triage both cost this as "one line", from a prose sentence of about 200 bytes. Under the corpus's 120-byte line cap, the ruled content does not fit on one line: naming the probe command alone costs 39 bytes, the three verbs 23, and a complete spelling with the failure evidence measures 185-190 bytes, i.e. two lines and therefore two payments.

Rather than take a second deletion, the wording was compressed to fit one line, and two things were dropped to do it:

  • the evidence clause (a shallow clone answers with the horizon commit, exit 0, plausible sha, plausible date, no warning) — it lives in the card and in the transcript below;
  • the deepen spelling (git fetch --unshallow, or --deepen=N until the answer stops moving) — the line says 加深, which is the same verb the file already uses for this at line 158.

Naming that trade rather than silently making the line longer, because it is the maintainer's call whether the evidence is worth a second payment.

Reverse verification of the guard's claim

Throwaway clone, made and deleted inside this run; never one of the shared checkouts. Cloned over file:// from the container's own mirror so --depth is honoured and no network is used.

$ git clone --depth 50 --branch main file:///home/user/objectstack /home/user/os-14793-shallow-probe
$ git -C ... rev-parse --is-shallow-repository
true
$ git -C ... rev-list --count HEAD
50
$ git log -S'Pure bug fixes do' --format='%h %ad %s' --date=short -- AGENTS.md
b72db01 2026-08-26 fix(spec,core): PluginHealthMonitor stops claiming a restart it never performed ... (#12589)
log exit=0
# that sha is the clone's own boundary commit, not an answer about AGENTS.md:
$ git log --format='%h %ad %s' --date=short -1 $(git rev-list --max-parents=0 HEAD | head -1)
b72db01 2026-08-26 fix(spec,core): PluginHealthMonitor stops claiming a restart it never performed ...
$ git fetch --unshallow
$ git rev-parse --is-shallow-repository
false
$ git rev-list --count HEAD
11462
$ git log -S'Pure bug fixes do' --format='%h %ad %s' --date=short -- AGENTS.md
43625fbd6 2026-05-30 docs: update agent instructions and launch configs
log exit=0

88 days of error, same exit code, no warning, and the wrong answer is a real commit with a real date. The full-history checkout this PR was written in independently reports 43625fbd6c 2026-05-30, matching the card's own measurement.

The framing correction, measured again here

The card's reassurance that the shared checkout "is no longer shallow" was a property of one container. Measured in this one, before any edit:

/home/user/objectstack : is-shallow-repository = false (12142 commits)
/home/user/objectui : is-shallow-repository = true (50 commits)

Two mirrors, one container, opposite answers. Neither "the checkout is deep now" nor "every container is shallow" is a durable fact about a given tree, which is the argument for a guard that says check, per repo, at the moment you are about to date something rather than one that says deepen at startup.

The pm-dispatch site, named and not added

Measured rather than assumed, and the answer inverts the card's evidence. The card's "0 occurrences of shallow" is a grep for the ASCII word; the corpus spells it in Chinese. .claude/skills/pm-dispatch/** already carries the rule in two places:

  • references/platform-readings.md, in 读数五坑: 浅检出上的历史读数不可信 naming merge-base --is-ancestor, rev-list --count and branch -r --contains, with the remedy 先 --deepen 再判,或走 REST compare.
  • SKILL.md's tool table row for scripts/pm/git-history.mjs: 窗口化 commit 计数:回答或 REFUSE —— 浅 clone 对窗口化 git log/rev-list 以 exit 0 无警告答错, plus historyHorizon() as a read-only predicate for self-answering tools.

So the PM half is not a gap and no second ratchet needs paying. What was uncovered was the os-dev half and the specific verb set: neither site names -S, --follow or blame, which are the dating verbs, and os-dev.md's single prior mention of a shallow checkout (line 157) is about dispatch-gates.mjsrefusing loudly — the opposite signal from the silent one this clause guards.

One consistency note for the reviewer, since it looks like a contradiction and is not. scripts/pm/git-history.mjs records that --is-shallow-repository is the wrong predicate on its own: after a legitimate deepen a repo can still report true while answering the asked window exactly, so a guard that refused on it would refuse correct answers. This clause does not refuse on it — it deepens on it. For a windowed count you can prove coverage (the floor sits below the window) and refusing is avoidable; for a -S dating read there is no window to prove anything against, so deepen until the answer stops moving is the only sound rule. The same file's other finding is why the clause says 加深 and never --shallow-since: that flag deepens or shortens, exit 0, no warning.

Gates, all at c6d554a — 11 commands, all green, nothing left unmeasured

Union re-derived after the final commit: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (stderr confirms the answer is about this repo at this commit) returns 9 commands. All 9 were run, plus the 2 the dispatch named that the derivation does not carry.

commandexitverdict
pnpm check:pm-skill-ratchet0✓ check-skill-line-ratchet: .claude/agents/os-dev.md is 469 lines (ceiling 469; headroom 0). and ✓ ... widest table row is 0 bytes (pin 0; headroom 0). and ✓ check-skill-line-ratchet self-test: 111 cases pass. — identical verdicts before and after the edit
pnpm check:pm-governed-prose0✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces
pnpm check:pm-skill-id-lint0✓ check-skill-id-lint: 23 file(s) clean (pattern /#[0-9]{3,}/g).
pnpm check:skill-frame-sync0✓ check-skill-frame-sync: 4 copies of the decision frame are structurally isomorphic across 3 files
pnpm check:agent-model-declared0✓ check-agent-model-declared: 1 agent definition(s) under .claude/agents/ all declare a model
pnpm check:nul-bytes0check-nul-bytes: OK (scanned 8064 text file(s) ... no raw ASCII control bytes).
pnpm check:corpus-claim-drift0check-corpus-claim-drift: OK, no new claim sites beside a pinned spelling.
pnpm check:agent-test-spelling0✓ check-agent-test-spelling: 0 violations — 430 file(s)
pnpm check:doc-authoring0✓ doc authoring guard: sibling-package prose ids hold the baseline — 831 pinned site(s) across 231 file(s)
pnpm check:pm-governed-merges0✓ check-governed-merges --self-test: 243 assertions
pnpm --filter @objectstack/lint run check:doc-formula-expressions0✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 426 files / 1365 TS blocks judged clean by @objectstack/formula. — the first two attempts exited 3, PREREQUISITE NOT MET, read as NOT MEASURED rather than red; it became measurable only after building @objectstack/spec, @objectstack/formula and @objectstack/lint under the verify lock. This gate reads .claude as one of its four ROOTS, so it genuinely covers the edited file and was worth paying for.

Every exit code was captured by redirecting to a file before any pipe, and each verdict above is the gate's own printed line, never a bare $?.

Edit-landed-on-disk proof, independent of any tool's exit code:

readingbeforeafter
wc -l .claude/agents/os-dev.md469469
wc -c .claude/agents/os-dev.md4274042820
grep -c 'is-shallow-repository'01
grep -c of the deleted line, anchored10
git diff --stat1 file changed, 1 insertion(+), 1 deletion(-)

Control bytes: grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' over the edited file exits 1 (no match), beside the gate.

eslint, narrowed — with the three pieces

Not "not run": measured, and the narrowing is proved not to have excluded anything.

  1. Population read from eslint's own config, not from a guess. Every files: entry in eslint.config.mjs (lines 785, 971, 1015, 1054, 1103, 1172, 1212) is an extension glob over {ts,tsx,mts,cts,js,jsx,mjs,cjs}. A case-insensitive grep of the config for markdown, processor and the .md extension returns 0 hits — there is no markdown processor, so a .md file is not in the linted population at all.
  2. File count read from --format json.npx eslint --no-inline-config --format json .claude/agents/os-dev.md exits 0 and returns 1 result with errorCount: 0 and one message: File ignored because no matching configuration was supplied. The changed-file count inside the lint population is 0 of 1.
  3. Invariance for untouched files.grep -c projectService eslint.config.mjs is 0, and the config states it itself at line 328: no parserOptions.project, no typed @typescript-eslint rules. With no type-aware linting there is no cross-file verdict coupling, so a markdown file outside the population cannot move any untouched file's result.

Landing

.claude/agents/os-dev.md line 418 is held by PR #14779 (one line, in the decision-frame region). This hunk is at lines 232 and 301 and is disjoint from it; whichever lands later merges main in rather than rebasing, and both survive.

No changeset: this publishes nothing from any package, so the skip-changeset label is applied instead. Applied by union write (documentation, size/xs, skip-changeset) after reading the existing set, since this session has no additive REST endpoint; comparative read-back showed all three present, and a delayed re-read is owed because an immediate read-back cannot detect a later strip.

Draft, and it stays draft — governed .claude/**, human merge is the review record. Not flipped ready, not enqueued, no reviewers requested.


🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1

`git log -S`, `git log --follow` and `git blame` answer from the horizon
commit in a shallow clone — exit 0, plausible sha, plausible date, no
warning — so a dev asked to date a line of history can report a wrong
answer with no signal. One standing clause now requires
`git rev-parse --is-shallow-repository` before a date or an ordering is
trusted, deepening when it is true, and declaring it in the report.
Scoped to history reads only. A content read at a ref (`git show
ref:path`, `git grep … ref --`) is depth-safe and is deliberately not
caught by the wording.
Paid same-file at ratchet headroom 0: the Definition-of-done line
"用户可见的改动加 changeset。" is dropped as a strict restatement of basic
rule 4, which states the same obligation more precisely
("用户可见的改动需要 `.changeset/*.md`") at a more binding site.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@os-zhuang
os-zhuang marked this pull request as ready for review September 3, 2026 15:16
@os-zhuang
os-zhuang added this pull request to the merge queueSep 3, 2026
@hotlongClaude

Copy link
Copy Markdown
Contributor

Maintainer ruling, 2026-09-03, given in chat to the director seat (session_01WXyGTWPbbreqXow7Z2pZCk) on decision batch 2, item 1 (this PR, option A: merge as-is). Verbatim: 「同意」.

Executing on that instruction from the maintainer's own account: marking ready, bringing the branch up to date with main by a merge commit so CI re-validates the ratchet against today's tree (the PR's last CI run predates the merges since 03:00Z), then merging directly (squash) once the head is green. No approving review is submitted by any seat; the human merge is the review record for the governed path .claude/agents/os-dev.md.


Generated by Claude Code

Merged via the queue into main with commit 3386493Sep 3, 2026
36 of 37 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-14793-shallow-clone-history-guard branch September 3, 2026 15:44
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/xsskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

4 participants

@os-litant@hotlong@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

docs(agents): guard history dating against a shallow clone - #14808

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-14793-shallow-clone-history-guard
Sep 3, 2026
Merged

docs(agents): guard history dating against a shallow clone#14808
os-zhuang merged 1 commit into
mainfrom
claude/issue-14793-shallow-clone-history-guard

Conversation

@os-litant

@os-litantos-litant commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Fixes#14793

One standing clause added to .claude/agents/os-dev.md, paid same-file at ratchet headroom 0 (469 lines in, 469 lines out). Head sha for every reading below: c6d554a.

The line added — verbatim, at line 232

- **`log -S/--follow/blame` 判日期或先后前**,先 `git rev-parse --is-shallow-repository`,true 就加深申报。

119 UTF-8 bytes, under the corpus cap of 120.

It is phrased for history reads only. A content read at a ref (git show ref:path, git grep ... ref --) reads the tree at the tip, which a shallow clone does not truncate, so it is depth-safe and is deliberately not caught by the wording: the trigger is the three history verbs plus the two things you can get wrong from them, a date or an ordering.

Placement: the bullet immediately after the git family rule in the standing clauses. That neighbour is the file's other clause about a git command whose answer depends on repository state the reader did not check (there, shared refs and the stash; here, depth).

The line paid — verbatim, was line 303, in Definition of done

- 用户可见的改动加 changeset。

Why its removal is justified on its own merits

It is a strict restatement. Basic rule 4, lines 71-72, states the same obligation on the same subject, more precisely, at a more binding site:

4. **永不**编辑 `content/docs/releases/`、force-push、推 `main`、合并任何东西。用户可见
的改动需要 `.changeset/*.md`。

Same subject phrase, 用户可见的改动; the surviving spelling names the file glob (.changeset/*.md) where the deleted one said only "changeset". The surviving site is one of the six basic rules — the part of the file the contract itself treats as the rules that must never be missed. grep -c '用户可见' over the file was 2 before this PR and is 1 after: the fact is not lost, only its weaker copy. (The phrase 用户可见的改动 itself now greps to 0 because basic rule 4 wraps it across lines 71-72 — a line-scoped grep is exactly the wrong instrument for judging whether this fact survives, which is worth a reviewer's second look.)

The Definition of done also keeps a changeset step of its own: the skip-changeset bullet still makes the dev decide changeset-versus-label against a stated criterion (publishes nothing from any package), so a change that does not qualify for the label is routed straight back to basic rule 4.

Chosen over the two other restatements considered because its omission fails loudly: a missing changeset is a red Check Changeset gate on the PR, caught by CI within the same round.

Candidates rejected, and why

  1. 资源纪律 rule 6's ⛔ 永不把验证挂在后台 watcher 上然后停轮(禁令与两种合法终态见「干净收尾」)。 — also a self-admitted narrow copy of a rule whose home is 干净收尾 clause 4, and legal under the file's own "one rule, one home" convention. Rejected: it is a forward pointer across a long distance, so it carries real navigational value, and its omission fails silently (a stalled round nobody is woken for).
  2. The Definition of done's last bullet, 拆掉你启动的一切 —— dev server,以及你挂起的每一个后台 monitor(见下节)。 — restated as a principle by 干净收尾 clause 1, which sits directly below it. Rejected for the same asymmetry: a leaked monitor replays a whole report at the PM and nothing goes red.
  3. Re-wrapping the file's degenerate single-token wrap lines — 7 of them, at lines 21, 23, 65, 69, 397, 405 and 447 (, 期 +, , , ——, 怎么, 义),直). Refused outright: the maintainer's 2026-08-17 ruling makes deleting content the only legal currency for the line ratchet, and the file carries that ruling itself. (They remain a legitimate independent density repair under the 2026-08-29 ruling — worth up to about 7 lines of headroom on a file pinned at 469/469 — but that is a separate PR that buys no content, not a payment bundled with this one.)

A costing correction the card should carry

The card and the triage both cost this as "one line", from a prose sentence of about 200 bytes. Under the corpus's 120-byte line cap, the ruled content does not fit on one line: naming the probe command alone costs 39 bytes, the three verbs 23, and a complete spelling with the failure evidence measures 185-190 bytes, i.e. two lines and therefore two payments.

Rather than take a second deletion, the wording was compressed to fit one line, and two things were dropped to do it:

  • the evidence clause (a shallow clone answers with the horizon commit, exit 0, plausible sha, plausible date, no warning) — it lives in the card and in the transcript below;
  • the deepen spelling (git fetch --unshallow, or --deepen=N until the answer stops moving) — the line says 加深, which is the same verb the file already uses for this at line 158.

Naming that trade rather than silently making the line longer, because it is the maintainer's call whether the evidence is worth a second payment.

Reverse verification of the guard's claim

Throwaway clone, made and deleted inside this run; never one of the shared checkouts. Cloned over file:// from the container's own mirror so --depth is honoured and no network is used.

$ git clone --depth 50 --branch main file:///home/user/objectstack /home/user/os-14793-shallow-probe
$ git -C ... rev-parse --is-shallow-repository
true
$ git -C ... rev-list --count HEAD
50
$ git log -S'Pure bug fixes do' --format='%h %ad %s' --date=short -- AGENTS.md
b72db01 2026-08-26 fix(spec,core): PluginHealthMonitor stops claiming a restart it never performed ... (#12589)
log exit=0
# that sha is the clone's own boundary commit, not an answer about AGENTS.md:
$ git log --format='%h %ad %s' --date=short -1 $(git rev-list --max-parents=0 HEAD | head -1)
b72db01 2026-08-26 fix(spec,core): PluginHealthMonitor stops claiming a restart it never performed ...
$ git fetch --unshallow
$ git rev-parse --is-shallow-repository
false
$ git rev-list --count HEAD
11462
$ git log -S'Pure bug fixes do' --format='%h %ad %s' --date=short -- AGENTS.md
43625fbd6 2026-05-30 docs: update agent instructions and launch configs
log exit=0

88 days of error, same exit code, no warning, and the wrong answer is a real commit with a real date. The full-history checkout this PR was written in independently reports 43625fbd6c 2026-05-30, matching the card's own measurement.

The framing correction, measured again here

The card's reassurance that the shared checkout "is no longer shallow" was a property of one container. Measured in this one, before any edit:

/home/user/objectstack : is-shallow-repository = false (12142 commits)
/home/user/objectui : is-shallow-repository = true (50 commits)

Two mirrors, one container, opposite answers. Neither "the checkout is deep now" nor "every container is shallow" is a durable fact about a given tree, which is the argument for a guard that says check, per repo, at the moment you are about to date something rather than one that says deepen at startup.

The pm-dispatch site, named and not added

Measured rather than assumed, and the answer inverts the card's evidence. The card's "0 occurrences of shallow" is a grep for the ASCII word; the corpus spells it in Chinese. .claude/skills/pm-dispatch/** already carries the rule in two places:

  • references/platform-readings.md, in 读数五坑: 浅检出上的历史读数不可信 naming merge-base --is-ancestor, rev-list --count and branch -r --contains, with the remedy 先 --deepen 再判,或走 REST compare.
  • SKILL.md's tool table row for scripts/pm/git-history.mjs: 窗口化 commit 计数:回答或 REFUSE —— 浅 clone 对窗口化 git log/rev-list 以 exit 0 无警告答错, plus historyHorizon() as a read-only predicate for self-answering tools.

So the PM half is not a gap and no second ratchet needs paying. What was uncovered was the os-dev half and the specific verb set: neither site names -S, --follow or blame, which are the dating verbs, and os-dev.md's single prior mention of a shallow checkout (line 157) is about dispatch-gates.mjsrefusing loudly — the opposite signal from the silent one this clause guards.

One consistency note for the reviewer, since it looks like a contradiction and is not. scripts/pm/git-history.mjs records that --is-shallow-repository is the wrong predicate on its own: after a legitimate deepen a repo can still report true while answering the asked window exactly, so a guard that refused on it would refuse correct answers. This clause does not refuse on it — it deepens on it. For a windowed count you can prove coverage (the floor sits below the window) and refusing is avoidable; for a -S dating read there is no window to prove anything against, so deepen until the answer stops moving is the only sound rule. The same file's other finding is why the clause says 加深 and never --shallow-since: that flag deepens or shortens, exit 0, no warning.

Gates, all at c6d554a — 11 commands, all green, nothing left unmeasured

Union re-derived after the final commit: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (stderr confirms the answer is about this repo at this commit) returns 9 commands. All 9 were run, plus the 2 the dispatch named that the derivation does not carry.

commandexitverdict
pnpm check:pm-skill-ratchet0✓ check-skill-line-ratchet: .claude/agents/os-dev.md is 469 lines (ceiling 469; headroom 0). and ✓ ... widest table row is 0 bytes (pin 0; headroom 0). and ✓ check-skill-line-ratchet self-test: 111 cases pass. — identical verdicts before and after the edit
pnpm check:pm-governed-prose0✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces
pnpm check:pm-skill-id-lint0✓ check-skill-id-lint: 23 file(s) clean (pattern /#[0-9]{3,}/g).
pnpm check:skill-frame-sync0✓ check-skill-frame-sync: 4 copies of the decision frame are structurally isomorphic across 3 files
pnpm check:agent-model-declared0✓ check-agent-model-declared: 1 agent definition(s) under .claude/agents/ all declare a model
pnpm check:nul-bytes0check-nul-bytes: OK (scanned 8064 text file(s) ... no raw ASCII control bytes).
pnpm check:corpus-claim-drift0check-corpus-claim-drift: OK, no new claim sites beside a pinned spelling.
pnpm check:agent-test-spelling0✓ check-agent-test-spelling: 0 violations — 430 file(s)
pnpm check:doc-authoring0✓ doc authoring guard: sibling-package prose ids hold the baseline — 831 pinned site(s) across 231 file(s)
pnpm check:pm-governed-merges0✓ check-governed-merges --self-test: 243 assertions
pnpm --filter @objectstack/lint run check:doc-formula-expressions0✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 426 files / 1365 TS blocks judged clean by @objectstack/formula. — the first two attempts exited 3, PREREQUISITE NOT MET, read as NOT MEASURED rather than red; it became measurable only after building @objectstack/spec, @objectstack/formula and @objectstack/lint under the verify lock. This gate reads .claude as one of its four ROOTS, so it genuinely covers the edited file and was worth paying for.

Every exit code was captured by redirecting to a file before any pipe, and each verdict above is the gate's own printed line, never a bare $?.

Edit-landed-on-disk proof, independent of any tool's exit code:

readingbeforeafter
wc -l .claude/agents/os-dev.md469469
wc -c .claude/agents/os-dev.md4274042820
grep -c 'is-shallow-repository'01
grep -c of the deleted line, anchored10
git diff --stat1 file changed, 1 insertion(+), 1 deletion(-)

Control bytes: grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' over the edited file exits 1 (no match), beside the gate.

eslint, narrowed — with the three pieces

Not "not run": measured, and the narrowing is proved not to have excluded anything.

  1. Population read from eslint's own config, not from a guess. Every files: entry in eslint.config.mjs (lines 785, 971, 1015, 1054, 1103, 1172, 1212) is an extension glob over {ts,tsx,mts,cts,js,jsx,mjs,cjs}. A case-insensitive grep of the config for markdown, processor and the .md extension returns 0 hits — there is no markdown processor, so a .md file is not in the linted population at all.
  2. File count read from --format json.npx eslint --no-inline-config --format json .claude/agents/os-dev.md exits 0 and returns 1 result with errorCount: 0 and one message: File ignored because no matching configuration was supplied. The changed-file count inside the lint population is 0 of 1.
  3. Invariance for untouched files.grep -c projectService eslint.config.mjs is 0, and the config states it itself at line 328: no parserOptions.project, no typed @typescript-eslint rules. With no type-aware linting there is no cross-file verdict coupling, so a markdown file outside the population cannot move any untouched file's result.

Landing

.claude/agents/os-dev.md line 418 is held by PR #14779 (one line, in the decision-frame region). This hunk is at lines 232 and 301 and is disjoint from it; whichever lands later merges main in rather than rebasing, and both survive.

No changeset: this publishes nothing from any package, so the skip-changeset label is applied instead. Applied by union write (documentation, size/xs, skip-changeset) after reading the existing set, since this session has no additive REST endpoint; comparative read-back showed all three present, and a delayed re-read is owed because an immediate read-back cannot detect a later strip.

Draft, and it stays draft — governed .claude/**, human merge is the review record. Not flipped ready, not enqueued, no reviewers requested.


🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1

`git log -S`, `git log --follow` and `git blame` answer from the horizon
commit in a shallow clone — exit 0, plausible sha, plausible date, no
warning — so a dev asked to date a line of history can report a wrong
answer with no signal. One standing clause now requires
`git rev-parse --is-shallow-repository` before a date or an ordering is
trusted, deepening when it is true, and declaring it in the report.
Scoped to history reads only. A content read at a ref (`git show
ref:path`, `git grep … ref --`) is depth-safe and is deliberately not
caught by the wording.
Paid same-file at ratchet headroom 0: the Definition-of-done line
"用户可见的改动加 changeset。" is dropped as a strict restatement of basic
rule 4, which states the same obligation more precisely
("用户可见的改动需要 `.changeset/*.md`") at a more binding site.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@os-zhuang
os-zhuang marked this pull request as ready for review September 3, 2026 15:16
@os-zhuang
os-zhuang added this pull request to the merge queueSep 3, 2026
@hotlongClaude

Copy link
Copy Markdown
Contributor

Maintainer ruling, 2026-09-03, given in chat to the director seat (session_01WXyGTWPbbreqXow7Z2pZCk) on decision batch 2, item 1 (this PR, option A: merge as-is). Verbatim: 「同意」.

Executing on that instruction from the maintainer's own account: marking ready, bringing the branch up to date with main by a merge commit so CI re-validates the ratchet against today's tree (the PR's last CI run predates the merges since 03:00Z), then merging directly (squash) once the head is green. No approving review is submitted by any seat; the human merge is the review record for the governed path .claude/agents/os-dev.md.


Generated by Claude Code

Merged via the queue into main with commit 3386493Sep 3, 2026
36 of 37 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-14793-shallow-clone-history-guard branch September 3, 2026 15:44
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/xsskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

4 participants

@os-litant@hotlong@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(agents): guard history dating against a shallow clone - #14808

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-14793-shallow-clone-history-guard
Sep 3, 2026
Merged

docs(agents): guard history dating against a shallow clone#14808
os-zhuang merged 1 commit into
mainfrom
claude/issue-14793-shallow-clone-history-guard

Conversation

@os-litant

@os-litantos-litant commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Fixes#14793

One standing clause added to .claude/agents/os-dev.md, paid same-file at ratchet headroom 0 (469 lines in, 469 lines out). Head sha for every reading below: c6d554a.

The line added — verbatim, at line 232

- **`log -S/--follow/blame` 判日期或先后前**,先 `git rev-parse --is-shallow-repository`,true 就加深申报。

119 UTF-8 bytes, under the corpus cap of 120.

It is phrased for history reads only. A content read at a ref (git show ref:path, git grep ... ref --) reads the tree at the tip, which a shallow clone does not truncate, so it is depth-safe and is deliberately not caught by the wording: the trigger is the three history verbs plus the two things you can get wrong from them, a date or an ordering.

Placement: the bullet immediately after the git family rule in the standing clauses. That neighbour is the file's other clause about a git command whose answer depends on repository state the reader did not check (there, shared refs and the stash; here, depth).

The line paid — verbatim, was line 303, in Definition of done

- 用户可见的改动加 changeset。

Why its removal is justified on its own merits

It is a strict restatement. Basic rule 4, lines 71-72, states the same obligation on the same subject, more precisely, at a more binding site:

4. **永不**编辑 `content/docs/releases/`、force-push、推 `main`、合并任何东西。用户可见
的改动需要 `.changeset/*.md`。

Same subject phrase, 用户可见的改动; the surviving spelling names the file glob (.changeset/*.md) where the deleted one said only "changeset". The surviving site is one of the six basic rules — the part of the file the contract itself treats as the rules that must never be missed. grep -c '用户可见' over the file was 2 before this PR and is 1 after: the fact is not lost, only its weaker copy. (The phrase 用户可见的改动 itself now greps to 0 because basic rule 4 wraps it across lines 71-72 — a line-scoped grep is exactly the wrong instrument for judging whether this fact survives, which is worth a reviewer's second look.)

The Definition of done also keeps a changeset step of its own: the skip-changeset bullet still makes the dev decide changeset-versus-label against a stated criterion (publishes nothing from any package), so a change that does not qualify for the label is routed straight back to basic rule 4.

Chosen over the two other restatements considered because its omission fails loudly: a missing changeset is a red Check Changeset gate on the PR, caught by CI within the same round.

Candidates rejected, and why

  1. 资源纪律 rule 6's ⛔ 永不把验证挂在后台 watcher 上然后停轮(禁令与两种合法终态见「干净收尾」)。 — also a self-admitted narrow copy of a rule whose home is 干净收尾 clause 4, and legal under the file's own "one rule, one home" convention. Rejected: it is a forward pointer across a long distance, so it carries real navigational value, and its omission fails silently (a stalled round nobody is woken for).
  2. The Definition of done's last bullet, 拆掉你启动的一切 —— dev server,以及你挂起的每一个后台 monitor(见下节)。 — restated as a principle by 干净收尾 clause 1, which sits directly below it. Rejected for the same asymmetry: a leaked monitor replays a whole report at the PM and nothing goes red.
  3. Re-wrapping the file's degenerate single-token wrap lines — 7 of them, at lines 21, 23, 65, 69, 397, 405 and 447 (, 期 +, , , ——, 怎么, 义),直). Refused outright: the maintainer's 2026-08-17 ruling makes deleting content the only legal currency for the line ratchet, and the file carries that ruling itself. (They remain a legitimate independent density repair under the 2026-08-29 ruling — worth up to about 7 lines of headroom on a file pinned at 469/469 — but that is a separate PR that buys no content, not a payment bundled with this one.)

A costing correction the card should carry

The card and the triage both cost this as "one line", from a prose sentence of about 200 bytes. Under the corpus's 120-byte line cap, the ruled content does not fit on one line: naming the probe command alone costs 39 bytes, the three verbs 23, and a complete spelling with the failure evidence measures 185-190 bytes, i.e. two lines and therefore two payments.

Rather than take a second deletion, the wording was compressed to fit one line, and two things were dropped to do it:

  • the evidence clause (a shallow clone answers with the horizon commit, exit 0, plausible sha, plausible date, no warning) — it lives in the card and in the transcript below;
  • the deepen spelling (git fetch --unshallow, or --deepen=N until the answer stops moving) — the line says 加深, which is the same verb the file already uses for this at line 158.

Naming that trade rather than silently making the line longer, because it is the maintainer's call whether the evidence is worth a second payment.

Reverse verification of the guard's claim

Throwaway clone, made and deleted inside this run; never one of the shared checkouts. Cloned over file:// from the container's own mirror so --depth is honoured and no network is used.

$ git clone --depth 50 --branch main file:///home/user/objectstack /home/user/os-14793-shallow-probe
$ git -C ... rev-parse --is-shallow-repository
true
$ git -C ... rev-list --count HEAD
50
$ git log -S'Pure bug fixes do' --format='%h %ad %s' --date=short -- AGENTS.md
b72db01 2026-08-26 fix(spec,core): PluginHealthMonitor stops claiming a restart it never performed ... (#12589)
log exit=0
# that sha is the clone's own boundary commit, not an answer about AGENTS.md:
$ git log --format='%h %ad %s' --date=short -1 $(git rev-list --max-parents=0 HEAD | head -1)
b72db01 2026-08-26 fix(spec,core): PluginHealthMonitor stops claiming a restart it never performed ...
$ git fetch --unshallow
$ git rev-parse --is-shallow-repository
false
$ git rev-list --count HEAD
11462
$ git log -S'Pure bug fixes do' --format='%h %ad %s' --date=short -- AGENTS.md
43625fbd6 2026-05-30 docs: update agent instructions and launch configs
log exit=0

88 days of error, same exit code, no warning, and the wrong answer is a real commit with a real date. The full-history checkout this PR was written in independently reports 43625fbd6c 2026-05-30, matching the card's own measurement.

The framing correction, measured again here

The card's reassurance that the shared checkout "is no longer shallow" was a property of one container. Measured in this one, before any edit:

/home/user/objectstack : is-shallow-repository = false (12142 commits)
/home/user/objectui : is-shallow-repository = true (50 commits)

Two mirrors, one container, opposite answers. Neither "the checkout is deep now" nor "every container is shallow" is a durable fact about a given tree, which is the argument for a guard that says check, per repo, at the moment you are about to date something rather than one that says deepen at startup.

The pm-dispatch site, named and not added

Measured rather than assumed, and the answer inverts the card's evidence. The card's "0 occurrences of shallow" is a grep for the ASCII word; the corpus spells it in Chinese. .claude/skills/pm-dispatch/** already carries the rule in two places:

  • references/platform-readings.md, in 读数五坑: 浅检出上的历史读数不可信 naming merge-base --is-ancestor, rev-list --count and branch -r --contains, with the remedy 先 --deepen 再判,或走 REST compare.
  • SKILL.md's tool table row for scripts/pm/git-history.mjs: 窗口化 commit 计数:回答或 REFUSE —— 浅 clone 对窗口化 git log/rev-list 以 exit 0 无警告答错, plus historyHorizon() as a read-only predicate for self-answering tools.

So the PM half is not a gap and no second ratchet needs paying. What was uncovered was the os-dev half and the specific verb set: neither site names -S, --follow or blame, which are the dating verbs, and os-dev.md's single prior mention of a shallow checkout (line 157) is about dispatch-gates.mjsrefusing loudly — the opposite signal from the silent one this clause guards.

One consistency note for the reviewer, since it looks like a contradiction and is not. scripts/pm/git-history.mjs records that --is-shallow-repository is the wrong predicate on its own: after a legitimate deepen a repo can still report true while answering the asked window exactly, so a guard that refused on it would refuse correct answers. This clause does not refuse on it — it deepens on it. For a windowed count you can prove coverage (the floor sits below the window) and refusing is avoidable; for a -S dating read there is no window to prove anything against, so deepen until the answer stops moving is the only sound rule. The same file's other finding is why the clause says 加深 and never --shallow-since: that flag deepens or shortens, exit 0, no warning.

Gates, all at c6d554a — 11 commands, all green, nothing left unmeasured

Union re-derived after the final commit: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (stderr confirms the answer is about this repo at this commit) returns 9 commands. All 9 were run, plus the 2 the dispatch named that the derivation does not carry.

commandexitverdict
pnpm check:pm-skill-ratchet0✓ check-skill-line-ratchet: .claude/agents/os-dev.md is 469 lines (ceiling 469; headroom 0). and ✓ ... widest table row is 0 bytes (pin 0; headroom 0). and ✓ check-skill-line-ratchet self-test: 111 cases pass. — identical verdicts before and after the edit
pnpm check:pm-governed-prose0✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces
pnpm check:pm-skill-id-lint0✓ check-skill-id-lint: 23 file(s) clean (pattern /#[0-9]{3,}/g).
pnpm check:skill-frame-sync0✓ check-skill-frame-sync: 4 copies of the decision frame are structurally isomorphic across 3 files
pnpm check:agent-model-declared0✓ check-agent-model-declared: 1 agent definition(s) under .claude/agents/ all declare a model
pnpm check:nul-bytes0check-nul-bytes: OK (scanned 8064 text file(s) ... no raw ASCII control bytes).
pnpm check:corpus-claim-drift0check-corpus-claim-drift: OK, no new claim sites beside a pinned spelling.
pnpm check:agent-test-spelling0✓ check-agent-test-spelling: 0 violations — 430 file(s)
pnpm check:doc-authoring0✓ doc authoring guard: sibling-package prose ids hold the baseline — 831 pinned site(s) across 231 file(s)
pnpm check:pm-governed-merges0✓ check-governed-merges --self-test: 243 assertions
pnpm --filter @objectstack/lint run check:doc-formula-expressions0✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 426 files / 1365 TS blocks judged clean by @objectstack/formula. — the first two attempts exited 3, PREREQUISITE NOT MET, read as NOT MEASURED rather than red; it became measurable only after building @objectstack/spec, @objectstack/formula and @objectstack/lint under the verify lock. This gate reads .claude as one of its four ROOTS, so it genuinely covers the edited file and was worth paying for.

Every exit code was captured by redirecting to a file before any pipe, and each verdict above is the gate's own printed line, never a bare $?.

Edit-landed-on-disk proof, independent of any tool's exit code:

readingbeforeafter
wc -l .claude/agents/os-dev.md469469
wc -c .claude/agents/os-dev.md4274042820
grep -c 'is-shallow-repository'01
grep -c of the deleted line, anchored10
git diff --stat1 file changed, 1 insertion(+), 1 deletion(-)

Control bytes: grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' over the edited file exits 1 (no match), beside the gate.

eslint, narrowed — with the three pieces

Not "not run": measured, and the narrowing is proved not to have excluded anything.

  1. Population read from eslint's own config, not from a guess. Every files: entry in eslint.config.mjs (lines 785, 971, 1015, 1054, 1103, 1172, 1212) is an extension glob over {ts,tsx,mts,cts,js,jsx,mjs,cjs}. A case-insensitive grep of the config for markdown, processor and the .md extension returns 0 hits — there is no markdown processor, so a .md file is not in the linted population at all.
  2. File count read from --format json.npx eslint --no-inline-config --format json .claude/agents/os-dev.md exits 0 and returns 1 result with errorCount: 0 and one message: File ignored because no matching configuration was supplied. The changed-file count inside the lint population is 0 of 1.
  3. Invariance for untouched files.grep -c projectService eslint.config.mjs is 0, and the config states it itself at line 328: no parserOptions.project, no typed @typescript-eslint rules. With no type-aware linting there is no cross-file verdict coupling, so a markdown file outside the population cannot move any untouched file's result.

Landing

.claude/agents/os-dev.md line 418 is held by PR #14779 (one line, in the decision-frame region). This hunk is at lines 232 and 301 and is disjoint from it; whichever lands later merges main in rather than rebasing, and both survive.

No changeset: this publishes nothing from any package, so the skip-changeset label is applied instead. Applied by union write (documentation, size/xs, skip-changeset) after reading the existing set, since this session has no additive REST endpoint; comparative read-back showed all three present, and a delayed re-read is owed because an immediate read-back cannot detect a later strip.

Draft, and it stays draft — governed .claude/**, human merge is the review record. Not flipped ready, not enqueued, no reviewers requested.


🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1

`git log -S`, `git log --follow` and `git blame` answer from the horizon
commit in a shallow clone — exit 0, plausible sha, plausible date, no
warning — so a dev asked to date a line of history can report a wrong
answer with no signal. One standing clause now requires
`git rev-parse --is-shallow-repository` before a date or an ordering is
trusted, deepening when it is true, and declaring it in the report.
Scoped to history reads only. A content read at a ref (`git show
ref:path`, `git grep … ref --`) is depth-safe and is deliberately not
caught by the wording.
Paid same-file at ratchet headroom 0: the Definition-of-done line
"用户可见的改动加 changeset。" is dropped as a strict restatement of basic
rule 4, which states the same obligation more precisely
("用户可见的改动需要 `.changeset/*.md`") at a more binding site.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@os-zhuang
os-zhuang marked this pull request as ready for review September 3, 2026 15:16
@os-zhuang
os-zhuang added this pull request to the merge queueSep 3, 2026
@hotlongClaude

Copy link
Copy Markdown
Contributor

Maintainer ruling, 2026-09-03, given in chat to the director seat (session_01WXyGTWPbbreqXow7Z2pZCk) on decision batch 2, item 1 (this PR, option A: merge as-is). Verbatim: 「同意」.

Executing on that instruction from the maintainer's own account: marking ready, bringing the branch up to date with main by a merge commit so CI re-validates the ratchet against today's tree (the PR's last CI run predates the merges since 03:00Z), then merging directly (squash) once the head is green. No approving review is submitted by any seat; the human merge is the review record for the governed path .claude/agents/os-dev.md.


Generated by Claude Code

Merged via the queue into main with commit 3386493Sep 3, 2026
36 of 37 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-14793-shallow-clone-history-guard branch September 3, 2026 15:44
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/xsskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

4 participants

@os-litant@hotlong@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(agents): guard history dating against a shallow clone - #14808

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-14793-shallow-clone-history-guard
Sep 3, 2026
Merged

docs(agents): guard history dating against a shallow clone#14808
os-zhuang merged 1 commit into
mainfrom
claude/issue-14793-shallow-clone-history-guard

Conversation

@os-litant

@os-litantos-litant commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Fixes#14793

One standing clause added to .claude/agents/os-dev.md, paid same-file at ratchet headroom 0 (469 lines in, 469 lines out). Head sha for every reading below: c6d554a.

The line added — verbatim, at line 232

- **`log -S/--follow/blame` 判日期或先后前**,先 `git rev-parse --is-shallow-repository`,true 就加深申报。

119 UTF-8 bytes, under the corpus cap of 120.

It is phrased for history reads only. A content read at a ref (git show ref:path, git grep ... ref --) reads the tree at the tip, which a shallow clone does not truncate, so it is depth-safe and is deliberately not caught by the wording: the trigger is the three history verbs plus the two things you can get wrong from them, a date or an ordering.

Placement: the bullet immediately after the git family rule in the standing clauses. That neighbour is the file's other clause about a git command whose answer depends on repository state the reader did not check (there, shared refs and the stash; here, depth).

The line paid — verbatim, was line 303, in Definition of done

- 用户可见的改动加 changeset。

Why its removal is justified on its own merits

It is a strict restatement. Basic rule 4, lines 71-72, states the same obligation on the same subject, more precisely, at a more binding site:

4. **永不**编辑 `content/docs/releases/`、force-push、推 `main`、合并任何东西。用户可见
的改动需要 `.changeset/*.md`。

Same subject phrase, 用户可见的改动; the surviving spelling names the file glob (.changeset/*.md) where the deleted one said only "changeset". The surviving site is one of the six basic rules — the part of the file the contract itself treats as the rules that must never be missed. grep -c '用户可见' over the file was 2 before this PR and is 1 after: the fact is not lost, only its weaker copy. (The phrase 用户可见的改动 itself now greps to 0 because basic rule 4 wraps it across lines 71-72 — a line-scoped grep is exactly the wrong instrument for judging whether this fact survives, which is worth a reviewer's second look.)

The Definition of done also keeps a changeset step of its own: the skip-changeset bullet still makes the dev decide changeset-versus-label against a stated criterion (publishes nothing from any package), so a change that does not qualify for the label is routed straight back to basic rule 4.

Chosen over the two other restatements considered because its omission fails loudly: a missing changeset is a red Check Changeset gate on the PR, caught by CI within the same round.

Candidates rejected, and why

  1. 资源纪律 rule 6's ⛔ 永不把验证挂在后台 watcher 上然后停轮(禁令与两种合法终态见「干净收尾」)。 — also a self-admitted narrow copy of a rule whose home is 干净收尾 clause 4, and legal under the file's own "one rule, one home" convention. Rejected: it is a forward pointer across a long distance, so it carries real navigational value, and its omission fails silently (a stalled round nobody is woken for).
  2. The Definition of done's last bullet, 拆掉你启动的一切 —— dev server,以及你挂起的每一个后台 monitor(见下节)。 — restated as a principle by 干净收尾 clause 1, which sits directly below it. Rejected for the same asymmetry: a leaked monitor replays a whole report at the PM and nothing goes red.
  3. Re-wrapping the file's degenerate single-token wrap lines — 7 of them, at lines 21, 23, 65, 69, 397, 405 and 447 (, 期 +, , , ——, 怎么, 义),直). Refused outright: the maintainer's 2026-08-17 ruling makes deleting content the only legal currency for the line ratchet, and the file carries that ruling itself. (They remain a legitimate independent density repair under the 2026-08-29 ruling — worth up to about 7 lines of headroom on a file pinned at 469/469 — but that is a separate PR that buys no content, not a payment bundled with this one.)

A costing correction the card should carry

The card and the triage both cost this as "one line", from a prose sentence of about 200 bytes. Under the corpus's 120-byte line cap, the ruled content does not fit on one line: naming the probe command alone costs 39 bytes, the three verbs 23, and a complete spelling with the failure evidence measures 185-190 bytes, i.e. two lines and therefore two payments.

Rather than take a second deletion, the wording was compressed to fit one line, and two things were dropped to do it:

  • the evidence clause (a shallow clone answers with the horizon commit, exit 0, plausible sha, plausible date, no warning) — it lives in the card and in the transcript below;
  • the deepen spelling (git fetch --unshallow, or --deepen=N until the answer stops moving) — the line says 加深, which is the same verb the file already uses for this at line 158.

Naming that trade rather than silently making the line longer, because it is the maintainer's call whether the evidence is worth a second payment.

Reverse verification of the guard's claim

Throwaway clone, made and deleted inside this run; never one of the shared checkouts. Cloned over file:// from the container's own mirror so --depth is honoured and no network is used.

$ git clone --depth 50 --branch main file:///home/user/objectstack /home/user/os-14793-shallow-probe
$ git -C ... rev-parse --is-shallow-repository
true
$ git -C ... rev-list --count HEAD
50
$ git log -S'Pure bug fixes do' --format='%h %ad %s' --date=short -- AGENTS.md
b72db01 2026-08-26 fix(spec,core): PluginHealthMonitor stops claiming a restart it never performed ... (#12589)
log exit=0
# that sha is the clone's own boundary commit, not an answer about AGENTS.md:
$ git log --format='%h %ad %s' --date=short -1 $(git rev-list --max-parents=0 HEAD | head -1)
b72db01 2026-08-26 fix(spec,core): PluginHealthMonitor stops claiming a restart it never performed ...
$ git fetch --unshallow
$ git rev-parse --is-shallow-repository
false
$ git rev-list --count HEAD
11462
$ git log -S'Pure bug fixes do' --format='%h %ad %s' --date=short -- AGENTS.md
43625fbd6 2026-05-30 docs: update agent instructions and launch configs
log exit=0

88 days of error, same exit code, no warning, and the wrong answer is a real commit with a real date. The full-history checkout this PR was written in independently reports 43625fbd6c 2026-05-30, matching the card's own measurement.

The framing correction, measured again here

The card's reassurance that the shared checkout "is no longer shallow" was a property of one container. Measured in this one, before any edit:

/home/user/objectstack : is-shallow-repository = false (12142 commits)
/home/user/objectui : is-shallow-repository = true (50 commits)

Two mirrors, one container, opposite answers. Neither "the checkout is deep now" nor "every container is shallow" is a durable fact about a given tree, which is the argument for a guard that says check, per repo, at the moment you are about to date something rather than one that says deepen at startup.

The pm-dispatch site, named and not added

Measured rather than assumed, and the answer inverts the card's evidence. The card's "0 occurrences of shallow" is a grep for the ASCII word; the corpus spells it in Chinese. .claude/skills/pm-dispatch/** already carries the rule in two places:

  • references/platform-readings.md, in 读数五坑: 浅检出上的历史读数不可信 naming merge-base --is-ancestor, rev-list --count and branch -r --contains, with the remedy 先 --deepen 再判,或走 REST compare.
  • SKILL.md's tool table row for scripts/pm/git-history.mjs: 窗口化 commit 计数:回答或 REFUSE —— 浅 clone 对窗口化 git log/rev-list 以 exit 0 无警告答错, plus historyHorizon() as a read-only predicate for self-answering tools.

So the PM half is not a gap and no second ratchet needs paying. What was uncovered was the os-dev half and the specific verb set: neither site names -S, --follow or blame, which are the dating verbs, and os-dev.md's single prior mention of a shallow checkout (line 157) is about dispatch-gates.mjsrefusing loudly — the opposite signal from the silent one this clause guards.

One consistency note for the reviewer, since it looks like a contradiction and is not. scripts/pm/git-history.mjs records that --is-shallow-repository is the wrong predicate on its own: after a legitimate deepen a repo can still report true while answering the asked window exactly, so a guard that refused on it would refuse correct answers. This clause does not refuse on it — it deepens on it. For a windowed count you can prove coverage (the floor sits below the window) and refusing is avoidable; for a -S dating read there is no window to prove anything against, so deepen until the answer stops moving is the only sound rule. The same file's other finding is why the clause says 加深 and never --shallow-since: that flag deepens or shortens, exit 0, no warning.

Gates, all at c6d554a — 11 commands, all green, nothing left unmeasured

Union re-derived after the final commit: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (stderr confirms the answer is about this repo at this commit) returns 9 commands. All 9 were run, plus the 2 the dispatch named that the derivation does not carry.

commandexitverdict
pnpm check:pm-skill-ratchet0✓ check-skill-line-ratchet: .claude/agents/os-dev.md is 469 lines (ceiling 469; headroom 0). and ✓ ... widest table row is 0 bytes (pin 0; headroom 0). and ✓ check-skill-line-ratchet self-test: 111 cases pass. — identical verdicts before and after the edit
pnpm check:pm-governed-prose0✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces
pnpm check:pm-skill-id-lint0✓ check-skill-id-lint: 23 file(s) clean (pattern /#[0-9]{3,}/g).
pnpm check:skill-frame-sync0✓ check-skill-frame-sync: 4 copies of the decision frame are structurally isomorphic across 3 files
pnpm check:agent-model-declared0✓ check-agent-model-declared: 1 agent definition(s) under .claude/agents/ all declare a model
pnpm check:nul-bytes0check-nul-bytes: OK (scanned 8064 text file(s) ... no raw ASCII control bytes).
pnpm check:corpus-claim-drift0check-corpus-claim-drift: OK, no new claim sites beside a pinned spelling.
pnpm check:agent-test-spelling0✓ check-agent-test-spelling: 0 violations — 430 file(s)
pnpm check:doc-authoring0✓ doc authoring guard: sibling-package prose ids hold the baseline — 831 pinned site(s) across 231 file(s)
pnpm check:pm-governed-merges0✓ check-governed-merges --self-test: 243 assertions
pnpm --filter @objectstack/lint run check:doc-formula-expressions0✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 426 files / 1365 TS blocks judged clean by @objectstack/formula. — the first two attempts exited 3, PREREQUISITE NOT MET, read as NOT MEASURED rather than red; it became measurable only after building @objectstack/spec, @objectstack/formula and @objectstack/lint under the verify lock. This gate reads .claude as one of its four ROOTS, so it genuinely covers the edited file and was worth paying for.

Every exit code was captured by redirecting to a file before any pipe, and each verdict above is the gate's own printed line, never a bare $?.

Edit-landed-on-disk proof, independent of any tool's exit code:

readingbeforeafter
wc -l .claude/agents/os-dev.md469469
wc -c .claude/agents/os-dev.md4274042820
grep -c 'is-shallow-repository'01
grep -c of the deleted line, anchored10
git diff --stat1 file changed, 1 insertion(+), 1 deletion(-)

Control bytes: grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' over the edited file exits 1 (no match), beside the gate.

eslint, narrowed — with the three pieces

Not "not run": measured, and the narrowing is proved not to have excluded anything.

  1. Population read from eslint's own config, not from a guess. Every files: entry in eslint.config.mjs (lines 785, 971, 1015, 1054, 1103, 1172, 1212) is an extension glob over {ts,tsx,mts,cts,js,jsx,mjs,cjs}. A case-insensitive grep of the config for markdown, processor and the .md extension returns 0 hits — there is no markdown processor, so a .md file is not in the linted population at all.
  2. File count read from --format json.npx eslint --no-inline-config --format json .claude/agents/os-dev.md exits 0 and returns 1 result with errorCount: 0 and one message: File ignored because no matching configuration was supplied. The changed-file count inside the lint population is 0 of 1.
  3. Invariance for untouched files.grep -c projectService eslint.config.mjs is 0, and the config states it itself at line 328: no parserOptions.project, no typed @typescript-eslint rules. With no type-aware linting there is no cross-file verdict coupling, so a markdown file outside the population cannot move any untouched file's result.

Landing

.claude/agents/os-dev.md line 418 is held by PR #14779 (one line, in the decision-frame region). This hunk is at lines 232 and 301 and is disjoint from it; whichever lands later merges main in rather than rebasing, and both survive.

No changeset: this publishes nothing from any package, so the skip-changeset label is applied instead. Applied by union write (documentation, size/xs, skip-changeset) after reading the existing set, since this session has no additive REST endpoint; comparative read-back showed all three present, and a delayed re-read is owed because an immediate read-back cannot detect a later strip.

Draft, and it stays draft — governed .claude/**, human merge is the review record. Not flipped ready, not enqueued, no reviewers requested.


🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1

`git log -S`, `git log --follow` and `git blame` answer from the horizon
commit in a shallow clone — exit 0, plausible sha, plausible date, no
warning — so a dev asked to date a line of history can report a wrong
answer with no signal. One standing clause now requires
`git rev-parse --is-shallow-repository` before a date or an ordering is
trusted, deepening when it is true, and declaring it in the report.
Scoped to history reads only. A content read at a ref (`git show
ref:path`, `git grep … ref --`) is depth-safe and is deliberately not
caught by the wording.
Paid same-file at ratchet headroom 0: the Definition-of-done line
"用户可见的改动加 changeset。" is dropped as a strict restatement of basic
rule 4, which states the same obligation more precisely
("用户可见的改动需要 `.changeset/*.md`") at a more binding site.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@os-zhuang
os-zhuang marked this pull request as ready for review September 3, 2026 15:16
@os-zhuang
os-zhuang added this pull request to the merge queueSep 3, 2026
@hotlongClaude

Copy link
Copy Markdown
Contributor

Maintainer ruling, 2026-09-03, given in chat to the director seat (session_01WXyGTWPbbreqXow7Z2pZCk) on decision batch 2, item 1 (this PR, option A: merge as-is). Verbatim: 「同意」.

Executing on that instruction from the maintainer's own account: marking ready, bringing the branch up to date with main by a merge commit so CI re-validates the ratchet against today's tree (the PR's last CI run predates the merges since 03:00Z), then merging directly (squash) once the head is green. No approving review is submitted by any seat; the human merge is the review record for the governed path .claude/agents/os-dev.md.


Generated by Claude Code

Merged via the queue into main with commit 3386493Sep 3, 2026
36 of 37 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-14793-shallow-clone-history-guard branch September 3, 2026 15:44
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/xsskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

4 participants

@os-litant@hotlong@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

docs(agents): guard history dating against a shallow clone - #14808

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-14793-shallow-clone-history-guard
Sep 3, 2026
Merged

docs(agents): guard history dating against a shallow clone#14808
os-zhuang merged 1 commit into
mainfrom
claude/issue-14793-shallow-clone-history-guard

Conversation

@os-litant

@os-litantos-litant commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Fixes#14793

One standing clause added to .claude/agents/os-dev.md, paid same-file at ratchet headroom 0 (469 lines in, 469 lines out). Head sha for every reading below: c6d554a.

The line added — verbatim, at line 232

- **`log -S/--follow/blame` 判日期或先后前**,先 `git rev-parse --is-shallow-repository`,true 就加深申报。

119 UTF-8 bytes, under the corpus cap of 120.

It is phrased for history reads only. A content read at a ref (git show ref:path, git grep ... ref --) reads the tree at the tip, which a shallow clone does not truncate, so it is depth-safe and is deliberately not caught by the wording: the trigger is the three history verbs plus the two things you can get wrong from them, a date or an ordering.

Placement: the bullet immediately after the git family rule in the standing clauses. That neighbour is the file's other clause about a git command whose answer depends on repository state the reader did not check (there, shared refs and the stash; here, depth).

The line paid — verbatim, was line 303, in Definition of done

- 用户可见的改动加 changeset。

Why its removal is justified on its own merits

It is a strict restatement. Basic rule 4, lines 71-72, states the same obligation on the same subject, more precisely, at a more binding site:

4. **永不**编辑 `content/docs/releases/`、force-push、推 `main`、合并任何东西。用户可见
的改动需要 `.changeset/*.md`。

Same subject phrase, 用户可见的改动; the surviving spelling names the file glob (.changeset/*.md) where the deleted one said only "changeset". The surviving site is one of the six basic rules — the part of the file the contract itself treats as the rules that must never be missed. grep -c '用户可见' over the file was 2 before this PR and is 1 after: the fact is not lost, only its weaker copy. (The phrase 用户可见的改动 itself now greps to 0 because basic rule 4 wraps it across lines 71-72 — a line-scoped grep is exactly the wrong instrument for judging whether this fact survives, which is worth a reviewer's second look.)

The Definition of done also keeps a changeset step of its own: the skip-changeset bullet still makes the dev decide changeset-versus-label against a stated criterion (publishes nothing from any package), so a change that does not qualify for the label is routed straight back to basic rule 4.

Chosen over the two other restatements considered because its omission fails loudly: a missing changeset is a red Check Changeset gate on the PR, caught by CI within the same round.

Candidates rejected, and why

  1. 资源纪律 rule 6's ⛔ 永不把验证挂在后台 watcher 上然后停轮(禁令与两种合法终态见「干净收尾」)。 — also a self-admitted narrow copy of a rule whose home is 干净收尾 clause 4, and legal under the file's own "one rule, one home" convention. Rejected: it is a forward pointer across a long distance, so it carries real navigational value, and its omission fails silently (a stalled round nobody is woken for).
  2. The Definition of done's last bullet, 拆掉你启动的一切 —— dev server,以及你挂起的每一个后台 monitor(见下节)。 — restated as a principle by 干净收尾 clause 1, which sits directly below it. Rejected for the same asymmetry: a leaked monitor replays a whole report at the PM and nothing goes red.
  3. Re-wrapping the file's degenerate single-token wrap lines — 7 of them, at lines 21, 23, 65, 69, 397, 405 and 447 (, 期 +, , , ——, 怎么, 义),直). Refused outright: the maintainer's 2026-08-17 ruling makes deleting content the only legal currency for the line ratchet, and the file carries that ruling itself. (They remain a legitimate independent density repair under the 2026-08-29 ruling — worth up to about 7 lines of headroom on a file pinned at 469/469 — but that is a separate PR that buys no content, not a payment bundled with this one.)

A costing correction the card should carry

The card and the triage both cost this as "one line", from a prose sentence of about 200 bytes. Under the corpus's 120-byte line cap, the ruled content does not fit on one line: naming the probe command alone costs 39 bytes, the three verbs 23, and a complete spelling with the failure evidence measures 185-190 bytes, i.e. two lines and therefore two payments.

Rather than take a second deletion, the wording was compressed to fit one line, and two things were dropped to do it:

  • the evidence clause (a shallow clone answers with the horizon commit, exit 0, plausible sha, plausible date, no warning) — it lives in the card and in the transcript below;
  • the deepen spelling (git fetch --unshallow, or --deepen=N until the answer stops moving) — the line says 加深, which is the same verb the file already uses for this at line 158.

Naming that trade rather than silently making the line longer, because it is the maintainer's call whether the evidence is worth a second payment.

Reverse verification of the guard's claim

Throwaway clone, made and deleted inside this run; never one of the shared checkouts. Cloned over file:// from the container's own mirror so --depth is honoured and no network is used.

$ git clone --depth 50 --branch main file:///home/user/objectstack /home/user/os-14793-shallow-probe
$ git -C ... rev-parse --is-shallow-repository
true
$ git -C ... rev-list --count HEAD
50
$ git log -S'Pure bug fixes do' --format='%h %ad %s' --date=short -- AGENTS.md
b72db01 2026-08-26 fix(spec,core): PluginHealthMonitor stops claiming a restart it never performed ... (#12589)
log exit=0
# that sha is the clone's own boundary commit, not an answer about AGENTS.md:
$ git log --format='%h %ad %s' --date=short -1 $(git rev-list --max-parents=0 HEAD | head -1)
b72db01 2026-08-26 fix(spec,core): PluginHealthMonitor stops claiming a restart it never performed ...
$ git fetch --unshallow
$ git rev-parse --is-shallow-repository
false
$ git rev-list --count HEAD
11462
$ git log -S'Pure bug fixes do' --format='%h %ad %s' --date=short -- AGENTS.md
43625fbd6 2026-05-30 docs: update agent instructions and launch configs
log exit=0

88 days of error, same exit code, no warning, and the wrong answer is a real commit with a real date. The full-history checkout this PR was written in independently reports 43625fbd6c 2026-05-30, matching the card's own measurement.

The framing correction, measured again here

The card's reassurance that the shared checkout "is no longer shallow" was a property of one container. Measured in this one, before any edit:

/home/user/objectstack : is-shallow-repository = false (12142 commits)
/home/user/objectui : is-shallow-repository = true (50 commits)

Two mirrors, one container, opposite answers. Neither "the checkout is deep now" nor "every container is shallow" is a durable fact about a given tree, which is the argument for a guard that says check, per repo, at the moment you are about to date something rather than one that says deepen at startup.

The pm-dispatch site, named and not added

Measured rather than assumed, and the answer inverts the card's evidence. The card's "0 occurrences of shallow" is a grep for the ASCII word; the corpus spells it in Chinese. .claude/skills/pm-dispatch/** already carries the rule in two places:

  • references/platform-readings.md, in 读数五坑: 浅检出上的历史读数不可信 naming merge-base --is-ancestor, rev-list --count and branch -r --contains, with the remedy 先 --deepen 再判,或走 REST compare.
  • SKILL.md's tool table row for scripts/pm/git-history.mjs: 窗口化 commit 计数:回答或 REFUSE —— 浅 clone 对窗口化 git log/rev-list 以 exit 0 无警告答错, plus historyHorizon() as a read-only predicate for self-answering tools.

So the PM half is not a gap and no second ratchet needs paying. What was uncovered was the os-dev half and the specific verb set: neither site names -S, --follow or blame, which are the dating verbs, and os-dev.md's single prior mention of a shallow checkout (line 157) is about dispatch-gates.mjsrefusing loudly — the opposite signal from the silent one this clause guards.

One consistency note for the reviewer, since it looks like a contradiction and is not. scripts/pm/git-history.mjs records that --is-shallow-repository is the wrong predicate on its own: after a legitimate deepen a repo can still report true while answering the asked window exactly, so a guard that refused on it would refuse correct answers. This clause does not refuse on it — it deepens on it. For a windowed count you can prove coverage (the floor sits below the window) and refusing is avoidable; for a -S dating read there is no window to prove anything against, so deepen until the answer stops moving is the only sound rule. The same file's other finding is why the clause says 加深 and never --shallow-since: that flag deepens or shortens, exit 0, no warning.

Gates, all at c6d554a — 11 commands, all green, nothing left unmeasured

Union re-derived after the final commit: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (stderr confirms the answer is about this repo at this commit) returns 9 commands. All 9 were run, plus the 2 the dispatch named that the derivation does not carry.

commandexitverdict
pnpm check:pm-skill-ratchet0✓ check-skill-line-ratchet: .claude/agents/os-dev.md is 469 lines (ceiling 469; headroom 0). and ✓ ... widest table row is 0 bytes (pin 0; headroom 0). and ✓ check-skill-line-ratchet self-test: 111 cases pass. — identical verdicts before and after the edit
pnpm check:pm-governed-prose0✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces
pnpm check:pm-skill-id-lint0✓ check-skill-id-lint: 23 file(s) clean (pattern /#[0-9]{3,}/g).
pnpm check:skill-frame-sync0✓ check-skill-frame-sync: 4 copies of the decision frame are structurally isomorphic across 3 files
pnpm check:agent-model-declared0✓ check-agent-model-declared: 1 agent definition(s) under .claude/agents/ all declare a model
pnpm check:nul-bytes0check-nul-bytes: OK (scanned 8064 text file(s) ... no raw ASCII control bytes).
pnpm check:corpus-claim-drift0check-corpus-claim-drift: OK, no new claim sites beside a pinned spelling.
pnpm check:agent-test-spelling0✓ check-agent-test-spelling: 0 violations — 430 file(s)
pnpm check:doc-authoring0✓ doc authoring guard: sibling-package prose ids hold the baseline — 831 pinned site(s) across 231 file(s)
pnpm check:pm-governed-merges0✓ check-governed-merges --self-test: 243 assertions
pnpm --filter @objectstack/lint run check:doc-formula-expressions0✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 426 files / 1365 TS blocks judged clean by @objectstack/formula. — the first two attempts exited 3, PREREQUISITE NOT MET, read as NOT MEASURED rather than red; it became measurable only after building @objectstack/spec, @objectstack/formula and @objectstack/lint under the verify lock. This gate reads .claude as one of its four ROOTS, so it genuinely covers the edited file and was worth paying for.

Every exit code was captured by redirecting to a file before any pipe, and each verdict above is the gate's own printed line, never a bare $?.

Edit-landed-on-disk proof, independent of any tool's exit code:

readingbeforeafter
wc -l .claude/agents/os-dev.md469469
wc -c .claude/agents/os-dev.md4274042820
grep -c 'is-shallow-repository'01
grep -c of the deleted line, anchored10
git diff --stat1 file changed, 1 insertion(+), 1 deletion(-)

Control bytes: grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' over the edited file exits 1 (no match), beside the gate.

eslint, narrowed — with the three pieces

Not "not run": measured, and the narrowing is proved not to have excluded anything.

  1. Population read from eslint's own config, not from a guess. Every files: entry in eslint.config.mjs (lines 785, 971, 1015, 1054, 1103, 1172, 1212) is an extension glob over {ts,tsx,mts,cts,js,jsx,mjs,cjs}. A case-insensitive grep of the config for markdown, processor and the .md extension returns 0 hits — there is no markdown processor, so a .md file is not in the linted population at all.
  2. File count read from --format json.npx eslint --no-inline-config --format json .claude/agents/os-dev.md exits 0 and returns 1 result with errorCount: 0 and one message: File ignored because no matching configuration was supplied. The changed-file count inside the lint population is 0 of 1.
  3. Invariance for untouched files.grep -c projectService eslint.config.mjs is 0, and the config states it itself at line 328: no parserOptions.project, no typed @typescript-eslint rules. With no type-aware linting there is no cross-file verdict coupling, so a markdown file outside the population cannot move any untouched file's result.

Landing

.claude/agents/os-dev.md line 418 is held by PR #14779 (one line, in the decision-frame region). This hunk is at lines 232 and 301 and is disjoint from it; whichever lands later merges main in rather than rebasing, and both survive.

No changeset: this publishes nothing from any package, so the skip-changeset label is applied instead. Applied by union write (documentation, size/xs, skip-changeset) after reading the existing set, since this session has no additive REST endpoint; comparative read-back showed all three present, and a delayed re-read is owed because an immediate read-back cannot detect a later strip.

Draft, and it stays draft — governed .claude/**, human merge is the review record. Not flipped ready, not enqueued, no reviewers requested.


🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1

`git log -S`, `git log --follow` and `git blame` answer from the horizon
commit in a shallow clone — exit 0, plausible sha, plausible date, no
warning — so a dev asked to date a line of history can report a wrong
answer with no signal. One standing clause now requires
`git rev-parse --is-shallow-repository` before a date or an ordering is
trusted, deepening when it is true, and declaring it in the report.
Scoped to history reads only. A content read at a ref (`git show
ref:path`, `git grep … ref --`) is depth-safe and is deliberately not
caught by the wording.
Paid same-file at ratchet headroom 0: the Definition-of-done line
"用户可见的改动加 changeset。" is dropped as a strict restatement of basic
rule 4, which states the same obligation more precisely
("用户可见的改动需要 `.changeset/*.md`") at a more binding site.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@os-zhuang
os-zhuang marked this pull request as ready for review September 3, 2026 15:16
@os-zhuang
os-zhuang added this pull request to the merge queueSep 3, 2026
@hotlongClaude

Copy link
Copy Markdown
Contributor

Maintainer ruling, 2026-09-03, given in chat to the director seat (session_01WXyGTWPbbreqXow7Z2pZCk) on decision batch 2, item 1 (this PR, option A: merge as-is). Verbatim: 「同意」.

Executing on that instruction from the maintainer's own account: marking ready, bringing the branch up to date with main by a merge commit so CI re-validates the ratchet against today's tree (the PR's last CI run predates the merges since 03:00Z), then merging directly (squash) once the head is green. No approving review is submitted by any seat; the human merge is the review record for the governed path .claude/agents/os-dev.md.


Generated by Claude Code

Merged via the queue into main with commit 3386493Sep 3, 2026
36 of 37 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-14793-shallow-clone-history-guard branch September 3, 2026 15:44
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/xsskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

4 participants

@os-litant@hotlong@os-zhuang@claude