Skip to content

fix(docs): a 200 is not the page you asked for, and an absence has a scope - #2191

Merged
kyle-sexton merged 2 commits into
mainfrom
gap/wrong-page-200
Aug 11, 2026
Merged

fix(docs): a 200 is not the page you asked for, and an absence has a scope#2191
kyle-sexton merged 2 commits into
mainfrom
gap/wrong-page-200

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

No linked issue

Summary

upstream-drift 1.2.0 shipped the fetch route yesterday (#2185).
#2187 applied it across 327
stamps today and surfaced two holes in it. Both are the route's own failure mode — a false
absence carrying every outward sign of a good read — one scope up from where 1.2.0 drew the line.

This is the convention's recheck discipline finding gaps in the convention, one release after it
shipped. upstream-drift 1.2.0 → 1.3.0, additive; no required part, canonical name, or
enforceability verdict changed. Pure docs/, so no plugin bump.

Fix

Hole 1 — a 200 does not mean you got the page you asked for

1.2.0's rung 1 guarded against truncation and against a channel that 404s. It did not guard against
a channel that succeeds with the wrong page. A retired slug is silently aliased to its
successor — no redirect, no Location, no notice in the body:

Probe Result
slash-commands.md 200, text/markdown, 82,668 bytes, first heading # Extend Claude with skills
skills.md 200, text/markdown, 82,668 bytes, same heading
SHA-256 of both a833dd5c96b9b111de0daec5fc6436e210c8cdc009e51306d32438746db0b5a5byte-identical
Rendered slash-commands 200, num_redirects: 0
nonexistent-page-xyz.md 404 — so this is not a catch-all; aliasing is specific to slugs that once existed

This outranks truncation as a failure. Truncation at least yields text you can see is short.
Here a search for a term the requested page owns comes back empty against a full, healthy-looking
body. Identity is therefore now part of rung 1, with two cheap checks:

  • Confirm the slug against llms.txt. Verified across ten slugs: the nine live ones each
    appear as docs/en/<slug>.md; slash-commands appears in no such entry (only an unrelated
    agent-sdk/slash-commands) — exactly the one that aliased. A mechanical detector, not a judgment
    call.
  • Read the body's first heading before quoting it. A heading that does not match the page you
    asked for ends the read. A title merely worded differently from the slug does not —
    sub-agents.md is titled "Create custom subagents", costs.md "Manage costs effectively"; both
    are correct pages.

A missing slug is not a dead end — it is a prompt to find the successor in the index and cite
that slug. Left unchecked, a citation of a retired slug keeps working indefinitely while
pointing somewhere its author never read, then becomes a 404 on a claim nobody re-derived the day
the alias is dropped.

Hole 2 — an absence claim now carries its scope

1.2.0 said a truncated read supports no absence claim. It never said a complete read of one page
supports no claim about the product. Two moves break it:

  • Widening the subject. Searching hooks and concluding "Claude Code has no X" asserts
    something about every page not searched. The honest form names the corpus: "not documented on
    hooks" — or, if the sweep genuinely covered the index, "not documented on any page listed in
    llms.txt as of <date>", a far larger and more expensive claim.

  • Searching the phrase instead of the capability. Verified on hooks.md: the phrase
    "verbose hooks" appears zero times, while the same page documents

    Async hook completion notifications are suppressed by default. To see them, enable verbose mode
    with Ctrl+O or start Claude Code with --verbose.

    and separately

    set CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose to see additional log lines such as hook matcher
    counts and query matching

    A phrase search returns nothing here and licenses "no verbose hooks toggle exists" — false, from a
    complete, untruncated read of the right page.

Stated as its own rule because it is the reason to care: a sound conclusion resting on a false
premise is fragile, not safe.
The instance above kept its conclusion on a corrected premise
(#2190); the next reader who
checks a false premise discards the conclusion with it. Fix the premise and keep the conclusion —
never keep a premise because the conclusion it props up is convenient.

Verification

Every claim above was re-derived here directly rather than taken on report, per the rule this
section states — a report of an absence is exactly the thing the convention says not to accept
second-hand:

  • Both .md bodies fetched and hashed locally; slash-commands/skills identity confirmed by
    matching SHA-256, byte count, and first heading
  • Redirect behavior probed with curl -L -w '%{num_redirects}' on the rendered URL
  • 404 control run against an invented slug, establishing the alias is not a catch-all
  • llms.txt fetched (187 docs/en/ entries) and checked slug-by-slug across all ten
  • hooks.md fetched in full; grep -ic "verbose hooks"0, and all four verbose mentions read
    verbatim
  • Nine live pages fetched to confirm the title-vs-slug check does not produce false positives

Gates (committed tree, CI form): check-contract-slice-prune.sh --check-diff origin/main,
check-changelog-parity.sh --check-bump origin/main, check-skill-portability.sh,
check-shell-portability.sh — all pass; markdownlint-cli2 over both changed files — 0 errors.
Remaining gates: CI is the authority.

Related

  • #2185 — shipped the fetch
    route this patches
  • #2187 — applied it at scale
    and found both holes
  • #2190 — withdrew the
    over-scoped nonexistence claim that hole 2 generalizes

…scope

upstream-drift 1.2.0 shipped the fetch route yesterday; #2187 applied it across
327 stamps today and surfaced two holes in it. Both are the route's own failure
mode — a false absence wearing the signs of a good read — one scope up from
where 1.2.0 drew the line.

A retired slug is silently aliased to its successor. slash-commands.md returns
200, text/markdown, 82,668 bytes titled "Extend Claude with skills",
byte-identical to skills.md (both a833dd5c...), with zero redirects and no
Location header. An invented slug still 404s, so this is specific to slugs that
once existed. Rung 1 guarded truncation and a 404ing channel but not a channel
that succeeds with the wrong page — and this is the worse failure, because a
search for a term the requested page owns returns empty against a complete,
healthy body. Identity joins rung 1: check the slug against llms.txt (nine live
slugs appear as docs/en/<slug>.md, the aliased one does not), and read the
body's first heading before quoting it.

Separately, 1.2.0 bounded absence claims against truncation but never against
scope. Searching one page and asserting the product is the same false negative
one level up, and searching a literal phrase rather than the capability is
another: "verbose hooks" appears zero times on hooks.md, which nonetheless
documents Ctrl+O, --verbose, and CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose. A phrase
search there licenses a false nonexistence claim from a complete read of the
right page. An absence claim now states its corpus and the terms tried.

Both verified here directly rather than taken on report, per the rule the
section states. Also records why the correction matters even when the conclusion
survives: a sound conclusion on a false premise is fragile, because the next
reader to check the premise discards the conclusion with it.
@cursor

cursor Bot commented Aug 11, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9928c5e8fe

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs/conventions/upstream-drift/README.md
@claude

claude Bot commented Aug 11, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 4s —— View job


Review complete

  • Gather context (PR body, changed files)
  • Read diff for both changed files
  • Review against CLAUDE.md / AGENTS.md and upstream-drift conventions
  • Post inline findings
  • Post summary comment

Findings

1 inline comment posted: docs/conventions/upstream-drift/README.md:130 — the new third bullet under "Reading the basis — the fetch route" leaves the intro sentence at line 123 ("Two rules bind every read...") stale — it's now three. Given the doc's own precision elsewhere ("Two checks", "Two moves" — both still accurate), this reads as an oversight rather than a rounding.

What checked out

  • Versioning: 1.2.0 → 1.3.0 is correctly a minor bump per the doc's own rule (additive guidance; no required part, canonical name, or enforceability verdict changed) — consistent between README.md and CHANGELOG.md headers.
  • No plugin bump needed: confirmed via grep -r "upstream-drift" -- '*.json' — no plugin.json references this doc, so the "pure docs/, no plugin bump" claim holds.
  • Anchors: the new #the-scope-of-an-absence self-link and the pre-existing #reading-the-basis--the-fetch-route anchor pattern (reused, not introduced, by this PR) both resolve correctly against GitHub's heading-slug algorithm.
  • Bullet counts elsewhere: "Two checks" (rung-1 identity checks) and "Two moves" (ways an absence claim breaks) both correctly enumerate two items each — no drift there.
  • SHA-256 hash: the a833dd5c…db0b5a5 hash in README.md is exactly 64 hex characters (valid SHA-256 length), and matches between README.md and its truncated form in CHANGELOG.md.
  • Heading hierarchy: the two new ### sections nest correctly under ## Reading the basis — the fetch route, no duplicate headings introduced (checked via full heading grep).
  • No plugin manifest/hook-contract/marketplace-schema surface touched — this PR is a prose edit to a conventions doc, so CLAUDE.md's fresh-docs WebFetch mandate doesn't gate it.

Not independently verified

  • The external claims in the diff (the slash-commands.md/skills.md aliasing, the llms.txt slug check, the hooks.md "verbose hooks" phrase-search result) are author-claimed and unverified by me — I did not re-fetch these upstream pages myself. Per this PR's own thesis (don't accept an absence/identity claim second-hand), these would need independent re-derivation to confirm, which is outside this docs-correctness review lane.
  • Could not run check-changelog-parity.sh --check-bump origin/main, check-contract-slice-prune.sh --check-diff origin/main, check-skill-portability.sh, check-shell-portability.sh, or markdownlint-cli2 myself — script execution required approval that wasn't grantable in this session. The PR body's claim that these gates pass is author-claimed and unverified. If you'd like me to actually run these, --allowedTools needs to permit executing scripts/*.sh and npx markdownlint-cli2.
  • MD013 (line length) is disabled repo-wide, so no lint concerns there regardless.

Comment thread docs/conventions/upstream-drift/README.md
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown

Claude has reviewed this PR 2 times. The lane skips further automatic reviews after 5; deleting this comment resets the count.

Review round 1, both reviewers independently. Adding the absence-scope rule to
the binding list left the intro reading 'Two rules bind every read'. On a
normative versioned convention a stale count is not cosmetic: it leaves a reader
unable to tell whether the third bullet binds or merely explains.

Audited every count phrase in the section rather than patching the reported one,
since this is the defect class the PR itself is about. 'Two checks, both cheap'
(2 bullets) and 'Two moves break it' (2 bullets) are both accurate; only the
intro was stale. The CHANGELOG's 1.2.0 entry keeps 'Two rules' -- it was true of
1.2.0 and history is never rewritten -- and the 1.3.0 entry now records the count
change so the transition is legible from the changelog alone.

The irony is noted and earned: a PR adding 'state the corpus and the terms tried'
shipped an unverified count in its own normative text.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FdBsorqGjCU4Mg5nrmgz4m
@claude

claude Bot commented Aug 11, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 55s —— View job


Review complete

  • Gather context (PR body, changed files, prior review comments)
  • Read current diff vs origin/main
  • Check whether prior findings (Codex + Claude: "Two rules" vs three bullets) were addressed in latest commit
  • Review against CLAUDE.md / AGENTS.md and upstream-drift conventions
  • Post inline findings (none — see below)
  • Post summary comment

Findings

None. The follow-up commit 6ca6f16 ("the binding-rule count says three, because three bind") fixes exactly the issue both Codex and my prior review flagged: README.md:123 now reads "Three rules bind every read" and the list at 123–132 correctly enumerates three bullets, the third of which cross-links to the new ### The scope of an absence section.

What I checked this pass

  • Rule count vs. list length: verified directly (not just re-reading the diff) — grep -n "^## \|^### " confirms ### The scope of an absence (L204) matches the intro's #the-scope-of-an-absence anchor, and the "Three rules" list at L123 has exactly 3 bullets.
  • Sub-list counts: "Two checks" (L181, the llms.txt-slug / first-heading checks) and "Two moves" (L206, widening the subject / phrase-vs-capability) each still correctly enumerate two items — no drift introduced by this commit.
  • Changelog version ordering: grep -n "^## " CHANGELOG.md shows 1.3.0 → 1.2.0 → 1.1.0 → 1.0.0, strictly newest-first with no duplicates — satisfies check-changelog-parity.sh --check-order's invariant (verified by manual heading inspection; could not execute the script itself in this sandbox — see below).
  • SHA-256 hash: independently counted — a833dd5c…db0b5a5 in README.md is exactly 64 hex characters.
  • Anchors: both README.md#reading-the-basis--the-fetch-route (referenced from CHANGELOG.md twice) and #the-scope-of-an-absence (self-link in README.md) resolve to real headings.
  • No plugin manifest touched: this is docs-only under docs/conventions/upstream-drift/; check-changelog-parity.sh --check-bump only scopes to plugins/*/.claude-plugin/plugin.json, so the convention's own semver bump (1.2.0 → 1.3.0) isn't covered by that gate at all — consistent with the PR body's "pure docs/, no plugin bump" claim.

Not independently verified

  • The external claims (slash-commands.md/skills.md aliasing and hash match, llms.txt slug check, hooks.md "verbose hooks" phrase-search result) remain author-claimed and unverified by me — outside this docs-correctness review's lane, per the PR's own thesis about not accepting absence/identity claims second-hand.
  • Could not execute check-changelog-parity.sh --check-order, check-contract-slice-prune.sh, check-skill-portability.sh, check-shell-portability.sh, or markdownlint-cli2 — each required approval not grantable in this session. I substituted manual equivalents where feasible (heading-order grep above). The PR body's claim that these gates pass is author-claimed and unverified.

@kyle-sexton
kyle-sexton merged commit cd99f92 into main Aug 11, 2026
33 checks passed
@kyle-sexton
kyle-sexton deleted the gap/wrong-page-200 branch August 11, 2026 01:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant