Repository navigation
Description
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-yt8bwqdjrepro/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 checkActual
$ 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
khanakia commentedon Oct 11, 2026
Checked a local build with the fix (v0.1.8-0.20261005134822-071437eb982e+dirty) against the ubgo/auth repo:
ds checkexits 0: 2,138 citations ok, 191 info-level uncovered findings (unchanged), and no range, problem, stale or broken findings.ds refreshrewrote 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.khanakia commentedon Oct 11, 2026
Fixed in ds 0.1.8. Thanks for the detailed report and the variations table.
What changed for the cases in this issue:
lines=range past the end of the block (or one that is not a range, such aslines=abc) is now reported byds checkasrange, 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 asstale, and fixinglines=lets the nextds refreshwrite the copy (bug 136).ds refreshno longer skips a copy silently. It printsskipped doc.md:3: lines=1-46 is outside the block's 45 linesand a count, the same under--dry-run, and lists copies it wrote with only a link (a block overmax_lines, anat=with no snapshot) aslink only.env=was rendered from the wrong environment's def (bug 137), and ads:blockof a secret stayedstaleforever (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-lineds:blockoutside 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 bareds:defline 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.