●2.1.302 — Fork subagents are now on by default in claude -p, the Agent SDK and the desktop app. Turn them off with CLAUDE_CODE_FORK_SUBAGENT=0 (10/16)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 43 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…●2.1.302 — Fork subagents are now on by default in claude -p, the Agent SDK and the desktop app. Turn them off with CLAUDE_CODE_FORK_SUBAGENT=0 (10/16)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 43 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…
I Preloaded a Scheduled Task with head -150: 15.6 KB Read, 3.3 KB for Today
A scheduled task preloaded its daily notes with head -150, and line count turned out to be no budget at all. A date-block reader with a byte ceiling, and how to avoid dropping ideas nobody has used yet.
I wrote head -150 into a scheduled task's preload because I wanted the read size to stay flat however big the file grew.
One morning I finally measured what those 150 lines were: how many bytes, how many days, and where the cut fell.
The file is a daily research note: date headings stacked newest-first, 26,804 bytes over 244 lines. The first 150 lines came to 15,619 bytes, 58% of the file. The part I actually act on, the newest day's block, was 3,263 bytes, about 21% of what I had read.
The remaining 12.4 KB was the previous four days. Some sections of it matter, but most of it was read and not used.
Below: why cutting by lines is unstable, a small reader that cuts by date block, and the pitfall I almost fell into while changing it. As an indie developer who hands routine chores to scheduled tasks, I am sticking to numbers I measured on my own file.
150 lines is a line count, not a budget
First, a script that prints each date block's lines and bytes and shows where head -N lands. It assumes ## YYYY-MM-DD headings, one per day.
#!/usr/bin/env python3"""Per-date-block lines/bytes, and where head -N cuts."""import reimport syspath, head_n = sys.argv[1], int(sys.argv[2]) if len(sys.argv) > 2 else 150lines = open(path, encoding="utf-8").read().split("\n")blocks, cur = [], Nonefor no, line in enumerate(lines, 1): m = re.match(r"## (\d{4}-\d{2}-\d{2})", line) if m: cur = {"date": m.group(1), "start": no, "lines": 0, "bytes": 0} blocks.append(cur) if cur: cur["lines"] += 1 cur["bytes"] += len(line.encode("utf-8")) + 1for b in blocks: print(f'{b["date"]} start {b["start"]:>4} {b["lines"]:>3} lines {b["bytes"]:>5}B {b["bytes"]/b["lines"]:>6.1f}B/line')total = sum(len(l.encode("utf-8")) + 1 for l in lines)got = sum(len(l.encode("utf-8")) + 1 for l in lines[:head_n])print(f"head -{head_n}: {got}B / total {total}B ({got/total:.0%})")cut = next((b for b in reversed(blocks) if b["start"] <= head_n), None)if cut: print(f'cut lands in the {cut["date"]} block, line {head_n - cut["start"] + 1}: {lines[head_n-1][:40]}')
Bytes are counted with encode("utf-8"), not len(line), because Japanese characters take three bytes each. Counting characters hides most of the difference between lines.
Here is the output on my file.
Date block
Lines
Bytes
Per line
10-18 (newest)
31
3,263
105.3B
10-17
29
2,284
78.8B
10-16
30
2,539
84.6B
10-15
38
4,967
130.7B
10-14
38
4,648
122.3B
10-13
37
4,756
128.5B
10-12
38
4,195
110.4B
What surprised me is that similar line counts can differ by more than 2x in bytes: 10-17 is 29 lines and 2,284 bytes, 10-15 is 38 lines and 4,967 bytes. One day averages 78.8 bytes per line, another 130.7.
So what head -150 delivers swings by nearly 2x depending on how long the recent lines happened to be. "150 lines, so it's constant" only holds when line length is constant.
The cut lands somewhere meaningless
The last line of the same script showed something else: line 150 is line 18 of the 10-14 block, in the middle of a section, just before a note about analytics not being fetched that day.
Four days (10-18 to 10-15) fit cleanly, and the fifth is cut off mid-section. Because the newest day sits on top, nothing I needed was lost, but that was luck.
I also checked section sizes across the file: A (primary sources) 5,383 bytes, B (pain points) 6,105, C (demand) 1,250, D (evergreen) 1,106, E (deadlines) 2,003, F (today's instructions) 2,441, G (angle ideas) 6,082. F, the section I lean on every morning, is about a tenth of the file; A, B and G make up most of the weight.
Cutting by lines cannot see that imbalance. It made more sense to read in the same units the notes are written in: date blocks and sections.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦You can check, with one script, how many bytes a head -N preload really reads from your own file and where the cut lands
✦You can switch a growing daily-notes file to a reader that takes the newest day in full and only the needed sections of older days, with a byte ceiling you can log
✦You can spot, before cutting, that dropping old days also drops ideas nobody has used yet
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
This is the replacement. It prints the newest day in full, only the chosen sections of older days, and writes the byte count to stderr.
#!/usr/bin/env python3"""Read daily notes by date block.Newest day in full; older days only the listed sections (A-G).Usage: refpeek.py <file> [past_days=2] [sections=AE] [byte_ceiling=8000]"""import reimport sysdef load_blocks(path): blocks, cur = [], None with open(path, encoding="utf-8") as f: for line in f.read().split("\n"): m = re.match(r"## (\d{4}-\d{2}-\d{2})", line) if m: cur = {"date": m.group(1), "lines": [line]} blocks.append(cur) elif cur is not None: cur["lines"].append(line) return blocksdef pick_sections(lines, keep): out, on = [lines[0]], True for line in lines[1:]: m = re.match(r"### ([A-G])\.", line) if m: on = m.group(1) in keep if on: out.append(line) return outdef main(path, past_days=2, keep="AE", budget=8000): blocks = load_blocks(path) if not blocks: sys.exit("FATAL: zero '## YYYY-MM-DD' headings. The format may have changed") out = list(blocks[0]["lines"]) for b in blocks[1:1 + past_days]: out += pick_sections(b["lines"], set(keep)) text = "\n".join(out) size = len(text.encode("utf-8")) note = "" if size <= budget else f" over ceiling {budget}" sys.stderr.write(f"blocks={1 + min(past_days, len(blocks) - 1)} bytes={size}{note}\n") print(text)if __name__ == "__main__": a = sys.argv main(a[1], int(a[2]) if len(a) > 2 else 2, a[3] if len(a) > 3 else "AE", int(a[4]) if len(a) > 4 else 8000)
In the task's instructions, the single head -150 daily_notes.md line became:
A line such as blocks=3 bytes=5664 lands on stderr, so the run log shows how many bytes that morning's preload read. Going over the ceiling does not stop the output; it only appends "over ceiling". A preload that is cut short is harder to debug than one that read a bit too much and said so.
How the bytes moved
Same file, different numbers of past days and sections.
Reading mode
Days
Bytes
vs head -150
head -150 (before)
4 days + 18 lines of the 5th
15,619
100%
Newest day only
1
3,262
21%
Newest + 1 past day (A, E)
2
4,443
28%
Newest + 2 past days (A, E)
3
5,664
36%
Newest + 3 past days (A, E)
4
7,526
48%
Newest + 3 past days (A, E, B)
4
9,241
59%
Keeping only A (primary sources) and E (deadlines) from older days gives 5,664 bytes for three days, 36% of the old read. Sources are useful for catching corrections; deadlines carry across days.
Note that this table shows bytes read, not whether today's decisions were just as good. That is why the next check was needed.
The counterintuitive part: cutting by date also cuts unused ideas
When I removed B (pain points) from older days, I assumed old B sections were already used up.
I looked at B for the past three days. The 10-17 and 10-16 sections were just "none today" notes, about 300 and 420 bytes. But 10-15 held four candidate ideas not yet turned into anything, 1,698 bytes.
With past_days=2, the whole 10-15 block is out of range. Drawing the line by date made those four ideas vanish from the preload. I also searched the names of what I produced afterwards for words matching those four; that is only a word match and not proof, but I found nothing.
"Drop old days" is not the same as "drop what is used up". The file is stacked by date, but an idea's lifetime has nothing to do with dates.
For now I split it like this:
Sections refreshed or valid across days (A, E) can be cut by date.
Idea sections (B, G) are cut by state: used ideas get a mark, and only unmarked ones are read for a few days.
The mark is just [used] appended to the idea's line, because other tasks read the same file and I do not want to change its format much.
The state-based reader for idea sections is still a prototype. Whether hand-marking [used] survives in practice is something I will decide after a few weeks.
Pitfalls
A changed heading format makes the reader read nothing, silently. If the date heading turns into ## 2026/10/18 or # 2026-10-18, the regex finds zero blocks. The reader above calls sys.exit on zero; my first version printed nothing and ended, and the task reported success on an empty preload. Treat empty output as a failure.
Do not mix stderr into stdout. If the byte count goes through print, that line lands inside the preload text. Write log lines to sys.stderr and keep a fixed prefix (blocks=) so you can search for it later.
Section letters are hard-coded to A-G. If an ### H. appears, [A-G] will not match it and it will be treated as a continuation of the previous section. On the day a section is added, update the regex the same day; I wrote that into the task's instructions.
The byte ceiling is yours to choose. 8,000 bytes came from my file, where "newest + 3 past days (A, E)" was 7,526 bytes. Run blockstat.py on your own file first and decide from its shape.
What to try next
First, measure what your head -N or tail -N preload actually carries on your own file, with a short script like blockstat.py. If date blocks differ by nearly 2x in bytes, the line limit is not standing in for a budget.
Then, before dropping old days, check whether any ideas nobody has used yet are still sitting in them. Had I not checked, I would have let go of four ideas without knowing it.
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.