Skip to content

lines= fragment past the block end is never rewritten and check reports it as permanently stale #4

Closed

Description

@khanakia

Summary

In repo mode, a ds:block with a lines=a-b fragment whose end is past the end of the block never gets its copy rewritten. ds refresh reports success, ds check reports stale ... run ds refresh forever, and ds ack does not clear it. The only message that names the real problem (lines=1-46 is outside the block's 45 lines) comes from ds render, not from check or refresh.

Versions: ds v0.1.8-0.20261005134124-242c7ba7135b (installed release) and a build of main at 071437e (v0.0.0-20261005134822-071437eb982e). Both behave the same.

Repro

repro/config.go, a struct whose block is 45 lines (type Config struct { through the closing brace, as ds locate counts it). After ds def inserts its ds:def comment above the type, the file looks like this:

package cfg

// Config holds server settings.
// ds:def id=config-yt8bwqdj
type Config struct {
	Field01 string
	Field02 string
	Field03 string
	Field04 string
	Field05 string
	Field06 string
	Field07 string
	Field08 string
	Field09 string
	Field10 string
	Field11 string
	Field12 string
	Field13 string
	Field14 string
	Field15 string
	Field16 string
	Field17 string
	Field18 string
	Field19 string
	Field20 string
	Field21 string
	Field22 string
	Field23 string
	Field24 string
	Field25 string
	Field26 string
	Field27 string
	Field28 string
	Field29 string
	Field30 string
	Field31 string
	Field32 string
	Field33 string
	Field34 string
	Field35 string
	Field36 string
	Field37 string
	Field38 string
	Field39 string
	Field40 string
	Field41 string
	Field42 string
	Field43 string
}

Setup:

cd repro
git init
ds init
# set [include] mode = "repo" in .ds/config.toml
ds def config.go#Config        # mints id config-yt8bwqdj

repro/doc.md, citing a fragment whose end (46) is one past the 45-line block:

# Config

<!-- ds:block id=config-yt8bwqdj lines=1-46 -->
<!-- /ds:block hash=000000 -->
ds refresh
ds ack config-yt8bwqdj --doc doc.md --line 3 --note ok
ds check
ds refresh
ds check

Actual

$ ds refresh
0 repo-mode copies rewritten
0 moved; ledger updated
$ ds ack config-yt8bwqdj --doc doc.md --line 3 --note ok
acked config-yt8bwqdj at doc.md:3 (human)
$ ds check
doc.md
  3	error    stale              copy rendered from 000000, block is now 3a9525
      fix: the copy at doc.md:3 was rendered from an older config-yt8bwqdj; run `ds refresh`
1 error
$ ds refresh
0 repo-mode copies rewritten
0 moved; ledger unchanged
$ ds check
(same stale error)

ds render doc.md is the only command that says why:

doc.md:3: lines=1-46 is outside the block's 45 lines

Expected

ds check reports the out-of-range fragment as its own error (for example bad-range: lines=1-46 is outside the block's 45 lines), not stale, and ds refresh says it skipped the copy and why. Following the printed fix (run ds refresh) currently leads nowhere.

Variations

Block is 45 lines, max_lines = 40, repo mode, one refresh then ack then check:

Directive Copy rewritten check
no lines= (45 lines) yes too-large: rendering 45 lines exceeds the cap of 40 (expected)
lines=1-40 yes ok
lines=1-41 yes too-large (expected, 41 over the cap)
lines=3-30 yes ok
lines=1-45 (equal to block end) yes too-large (expected)
lines=1-46 (one past the end) no stale, permanently

On a 43-line block, lines=1-44 and lines=1-45 showed the same permanent stale. So the trigger is only "range end greater than the block's line count"; ranges at or inside the block are fine and ack clears them normally.

Side observation, possibly intended: with lines=1-40 on a 45-line block, editing a field on line 43 (outside the fragment) still marks the citing sentence unacked with a diff of the outside line, because the hash covers the whole block rather than the fragment. Ack clears it, so this is only noise for fragment citations.

Suspected cause

This is a suspicion from reading the source, not a confirmed diagnosis. In render/render.go (around the keyLines handling, line 562), an out-of-range fragment calls r.note(n, "lines=%s is outside the block's %d lines", ...) and returns "", true. The note only reaches ds render. The empty result then leaves the copy unwritten in repo-mode refresh, so the copy keeps its old (or placeholder) hash and the staleness pass keeps flagging it. The check passes appear not to consume the render notes, so the real reason is lost.

Activity

  1. khanakia commented on Oct 11, 2026

    @khanakia
    MemberAuthor

    Checked a local build with the fix (v0.1.8-0.20261005134822-071437eb982e+dirty) against the ubgo/auth repo:

    • ds check exits 0: 2,138 citations ok, 191 info-level uncovered findings (unchanged), and no range, problem, stale or broken findings.
    • ds refresh rewrote 0 repo-mode copies, moved 0, skipped none, and left the ledger unchanged.

    That repo has no lines= range past its block's end, so it can't exercise the fix itself; the repro in the issue is the test for that. It does confirm the new range and secret checks raise no false positives on a large docs set. Leaving the issue open until the fix lands on main.

  2. khanakia commented on Oct 11, 2026

    @khanakia
    MemberAuthor

    Fixed in ds 0.1.8. Thanks for the detailed report and the variations table.

    What changed for the cases in this issue:

    • A lines= range past the end of the block (or one that is not a range, such as lines=abc) is now reported by ds check as range, an error in repo mode, with "adjust lines= at doc.md:3 to fit the block's 45 lines" as the fix. It is checked before the copy comparison, so it is never reported as stale, and fixing lines= lets the next ds refresh write the copy (bug 136).
    • ds refresh no longer skips a copy silently. It prints skipped doc.md:3: lines=1-46 is outside the block's 45 lines and a count, the same under --dry-run, and lists copies it wrote with only a link (a block over max_lines, an at= with no snapshot) as link only.
    • The same problem in two other places: a copy citing env= was rendered from the wrong environment's def (bug 137), and a ds:block of a secret stayed stale forever (bug 138).

    Found while testing this, and fixed in the same release: a ds: link inside a JSON string read as a citation (139), a whole-line ds:block outside a comment read as one in JSON, CSV and similar files (140), a copy already in a build-mode page never refreshed (141), a link inside a YAML, TOML or HCL comment ignored (142), and a bare ds:def line silently ignored in INI, Markdown and similar files (143).

    Your side observation, that a fragment citation is flagged when a line outside the fragment changes, is unchanged: the hash covers the whole block by design.

    To update, run ds update (a release build also updates itself once a day). The full list is in the changelog.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions