Ecosystem metrics

New Repos
25
New
Commits
528
down 6.4%
Releases
5
down 28.6%
Contributors
55
up 19.6%
Merges
19
up 46.2%

Repository Explorer

7119 commits in all time Jun 17, 2026 13:13 – Sep 15, 2026 13:13 UTC
larkiny nodekit
Merge 747be00b69be53622adc6f0ea8b5196af742bdd0 into f40ba97e346ad7408168cc0b2e2ebaf4b8770e00
Git Commit 846a6f55 Branch pull/176/merge Document 1/0 ++ 0 --
tasibot nodekit
Merge 47b79ceac31ec8187312b766b752e818cefb84c4 into bf9f72790009f84d50ba4dba769e85d04a627489
Git Commit 3f1dc411 Branch pull/193/merge Document 38/5,605 ++ 31 --
tasibot nodekit
chore: mark pflag as a direct dependency
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Git Commit 47b79cea Branch pull/193/head Document 1/1 ++ 1 --
tasibot nodekit
fix: keep crash output at every level, --level panic included
An entry with no readable level was dropped once the floor passed error,
which is the one place the exemption had to hold: a runtime panic is
written by the Go runtime and not through logrus, so it carries no level
field, and `logs --level panic` on a node that died of a nil dereference
answered with nothing at all while the default warn view showed the dump.
The floor now never judges a line it could not read a level from, which is
what the command's help has been promising.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqPSx1wqM8rMdo4EHPa4jy
Git Commit 94ef4392 Branch pull/193/head Document 4/41 ++ 20 --
tasibot nodekit
docs: record the truncation Follow cannot see
Size against offset only catches a truncation that is still visible when
the poll arrives. A file emptied and written back past the old offset
inside one interval never looks short, and the next drain reads new bytes
at a stale position: the prefix is lost and a pending fragment of the old
file is spliced onto what followed.

Polling has no way to close that. Nothing separates a truncate-and-regrow
from an append once the inode is unchanged and the length has recovered,
so the alternative is a different mechanism rather than a better check.
The window is one interval wide, algod rotates by renaming, and tail -F
has the same hole; it is left open on purpose, and now says so.

The truncation test slept to stay out of that window without explaining
which window, which read as timing noise rather than as the point.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqPSx1wqM8rMdo4EHPa4jy
Git Commit 8e061361 Branch pull/193/head Document 2/21 ++ 1 --
tasibot nodekit
docs: add the generated man page for logs
Every other registered top-level command has one, and the README's
command list is where the published docs link from. Generated with
cobra/doc and put through the same banner substitution the existing
pages carry, checked by regenerating nodekit_restart.md and diffing it
against the committed copy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NMKTAqwAPrGqB8hvgmBWu5
Git Commit f21b7562 Branch pull/193/head Document 2/51 ++ 0 --
tasibot nodekit
fix: finish the log permission message and split off --file
The message broke off at "adding your account to the 'algorand' group
after", and it blamed the algod data directory's ownership for every
permission failure, including one on a file the user named with --file. It
now finishes the instruction, and a file the user chose gets a message that
only says which path could not be read.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqPSx1wqM8rMdo4EHPa4jy
Git Commit 5c0e8388 Branch pull/193/head Document 2/14 ++ 3 --
tasibot nodekit
fix: resume follow at the start of a line algod was still writing
Scan reported the live log's size as the follow offset, but a log caught
mid-write ends mid-line: the scan shows that fragment, and Follow then
resumed past it, so the remainder of the line arrived as a line of its own
and the entry algod was writing never appeared whole. The offset is now
rounded back to the last line boundary, within one read buffer, so the
completed entry is delivered once the newline lands.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqPSx1wqM8rMdo4EHPa4jy
Git Commit bac25c1a Branch pull/193/head Document 2/102 ++ 8 --
tasibot nodekit
fix: report an archive that cannot be read instead of dropping it
ArchiveFiles skipped every path os.Stat failed on, permission denied
included. With a separate LogArchiveDir the user cannot enter, the
archive was removed before the scan ever saw it, so nothing recorded it
as skipped and no warning was printed: the command reported a complete
history it had never read, which is the one failure this package works
to avoid.

Only absence is skipped now. Anything else keeps the path and lets the
read path fail on it, which puts it in ScanResult.Skipped and gets it
warned about -- the same choice PruneSources already makes, for the same
reason. An archive whose age could not be read sorts last, so it cannot
reorder the history around it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqPSx1wqM8rMdo4EHPa4jy
Git Commit cb861914 Branch pull/193/head Document 2/57 ++ 5 --
tasibot nodekit
fix: quote a field name that would split the rendered line
Render's one-line guarantee was only half enforced. Values were escaped
and keys were concatenated raw, but a field name is decoded from the same
JSON object the value is, and an object key may hold anything a JSON
string can. A key carrying a newline split one entry across two physical
lines, which is the defect the value quoting was there to prevent.

The predicate moves into quoteIfNeeded and both halves of the pair go
through it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqPSx1wqM8rMdo4EHPa4jy
Git Commit 74f32c3c Branch pull/193/head Document 2/40 ++ 17 --
tasibot nodekit
fix: compare and merge every field of the algod config
IsEqual and MergeAlgodConfigs still handled only the two P2P flags, so the
log fields added for the logs command were invisible to both: two configs
differing only in BaseLoggerDebugLevel, LogSizeLimit or a log path compared
equal, and a value passed through the merge was dropped before anything
could write it. Both now cover every field, through a pair of generic
helpers rather than a block each, and a reflection-driven test fails on the
next field added to Config that either of them forgets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqPSx1wqM8rMdo4EHPa4jy
Git Commit ec09f55a Branch pull/193/head Document 2/101 ++ 29 --
tasibot nodekit
fix: stop --all warning that the view of the log is incomplete
--all names no level. It asks for whatever the log holds, and the log
holds everything the node wrote, so nothing the user asked for is
missing from it. Measuring the request against the node's floor anyway
meant --all asked for trace, which no algod writes, and every node with
a configured BaseLoggerDebugLevel was reported as withholding something
-- on the one run where the request was for all of it.

The warning stays for a level the user named and the node never writes,
which is what it is for. A node genuinely holding levels back is still
explained by reportEmptyLogResult, at the point where that leaves
nothing to show and the reason matters.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqPSx1wqM8rMdo4EHPa4jy
(cherry picked from commit 88bc94234b130effa60ab076a647f35d33e2e359)
Git Commit b862a3fa Branch pull/193/head Document 2/55 ++ 4 --
tasibot nodekit
docs: state that --since bounds the region read
The decision record still listed stopping the backward walk early on
--since as deliberately deferred, on the grounds that it would change
what the flag means for entries Keep never drops on time. Three
optimizations went in since and do exactly that: the walk stops once a
chunk's oldest line is older than the bound, a forward stream bisects to
the first entry at or after it, and an archive written entirely before
it is not opened at all, which is the only way to skip a compressed one.
Reviewers of #193 read the record against the code and found the
contradiction three times over, once per site.

The record was the stale half. --since bounds the region of the history
that is read, and an entry whose timestamp cannot be read is shown when
it falls in that region: a panic dump belongs to the moment the lines
around it were written, and one from two hours ago is not part of the
last fifteen minutes. Written down as a decision, with the reasoning
alongside the cost the three optimizations avoid, and cross-referenced
from each of them and from Filter.Keep. The sentence in sinceOffset
calling its answer "never a filter" was the specific claim that had
stopped being true.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NMKTAqwAPrGqB8hvgmBWu5
Git Commit f4a403f5 Branch pull/193/head Document 4/27 ++ 5 --
tasibot nodekit
fix: parse in full when --filter text contains <, > or &
The prescreen searches the undecoded line for the filter text verbatim,
which is only sound for bytes JSON never rewrites. Quotes, backslashes
and non-ASCII were excluded on that basis; `<`, `>` and `&` were not,
and they should have been. logrus writes its JSON through Go's encoder
with HTML escaping left on, so where the decoded message holds one of
those characters the line holds \u003c, \u003e or \u0026. `nodekit logs
--filter '<nil>'` therefore found nothing, however many entries said
<nil>.

Those three bytes now fall through to the full parse, along with the
rest of the text the raw search cannot be trusted on.

The corpus line and the two filters added to the one-sided prescreen
test fail without the fix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NMKTAqwAPrGqB8hvgmBWu5
Git Commit 849f4add Branch pull/193/head Document 2/16 ++ 3 --
tasibot nodekit
fix: keep a field value from breaking an entry across two lines
A string field was quoted only when it held a space or an equals sign,
so a value carrying a newline and neither was written out raw and split
one entry over two lines, which is exactly what Render promises not to
do and what makes the output greppable.

Nothing algod writes reaches this today: a stack trace goes into the
message, where collapseNewlines handles it, and the one structurally
multi-line field is built by a telemetry hook on a copy of the entry
that never reaches the file. The guarantee is Render's own, though, and
--file points the command at whatever file the user names, so the test
now covers control characters, quotes and backslashes alike -- %q
escapes them together and the guarantee stops depending on the producer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqPSx1wqM8rMdo4EHPa4jy
Git Commit 49da6a08 Branch pull/193/head Document 2/35 ++ 2 --
tasibot nodekit
docs: say that --follow starts from the newest ten
The Long text and the man page said every matching entry is shown, while
--follow quietly switched the default to ten so a long backlog would not
scroll past before the stream began. The exception is now written down in
both places and in the flag's own help, since the behaviour is the one tail
-f has and worth keeping.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqPSx1wqM8rMdo4EHPa4jy
Git Commit c06f179e Branch pull/193/head Document 2/6 ++ 2 --
tasibot nodekit
fix: bound the reader against a shrinking file and a runaway line
Two problems in reader.go, both raised in review of #193.

The backward walk sized the file, then read chunks of it with ReadAt and
ignored the count returned. A file truncated in place or replaced by a
shorter one after that stat hands back a short read with io.EOF, leaving
the tail of the buffer zeroed; those NUL bytes were then split into
lines, parsed as entries and charged against the scan budget. The walk
now takes only the bytes actually read, and drops the carry from the
chunk above with them, since the two halves no longer abut and splicing
them would invent a line neither file contains.

Follow read its lines with ReadBytes, which accumulates until it finds
the delimiter. The cap below it only ever saw a line that had already
been buffered whole, so a runaway line was bounded by the writer rather
than by us. It now reads the way forEachLine does, keeping at most
maxLineBytes across ErrBufferFull and discarding the rest, which is
where ParseLine would have sent it anyway.

The stale-size test fails without the first fix, emitting a 384-byte
entry of NULs. The follow test does not discriminate on output, since
the old path arrived at the same truncation by a more expensive route;
it guards the part of the rewrite that could go wrong, that the
discarded remainder does not disturb the line after it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NMKTAqwAPrGqB8hvgmBWu5
Git Commit 61fdb59e Branch pull/193/head Document 2/87 ++ 16 --
tasibot nodekit
fix: do not file a callback error under Skipped
streamOne returns a failure to read the file and a failure inside the
caller's callback down one path, and behind the live log streamSources
treated both as an unreadable archive. A callback that failed while an
archive was open had its error recorded as the archive's, and the scan
carried on into the newer files and returned nil.

That lost a cancellation or a closed pipe outright, and reported a hole
in a history that had been read perfectly well. The n > 0 path has always
returned the callback's error directly; the two halves of Scan disagreed
about their own contract. Callback errors are tagged so they can be told
apart and returned.

The existing test passed a single live source, so the branch that
swallowed them had never been read by it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqPSx1wqM8rMdo4EHPa4jy
Git Commit a7a87320 Branch pull/193/head Document 2/48 ++ 1 --
tasibot nodekit
fix: restart follow at the top of a log that shrank before it opened
The offset Scan reports is the live log's size at the moment it was read.
A rotation or a truncation between that and Follow opening the file leaves
the offset pointing into a file it was never measured against: the seek
lands past the end of the replacement, and the stream then waits for it to
grow back to a position that means nothing in it. The truncation check in
the poll loop only recovers this if the new file has not already grown past
the stale offset, so a file shorter than the offset is now read from its
start when it is opened.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqPSx1wqM8rMdo4EHPa4jy
Git Commit 8ab95ff9 Branch pull/193/head Document 2/51 ++ 3 --
tasibot nodekit
fix: stop a pruned archive turning into a node that never logged
reportEmptyLogResult counted the sources the scan opened, which is the
list after PruneSources has already run. A --since newer than every
archive collapsed that count to the live log alone, so an empty live log
reported "the log file is empty, the node has not logged anything yet"
against a node with a year of rotated history behind it -- and sent the
user off to *start* a node that was already running.

The count is now the unpruned history, which leaves LogsNoMatchMsg to
explain the result. That is also the true answer: an archive pruned for
predating --since held nothing the search wanted either.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqPSx1wqM8rMdo4EHPa4jy
Git Commit c516ca2d Branch pull/193/head Document 2/94 ++ 5 --
tasibot nodekit
feat: search the shown fields with --filter
--filter only looked at the message, but round numbers and peer and
account addresses are written as fields, and they are what people search
for. `--filter 49291042` found nothing on the entry rendered with
Round=49291042.

The search now covers each field Render prints, matched as decoded,
unquoted key=value text, so the value, the key or the pair all match. A
value Render quotes is searched without its quotes. Hidden fields stay
out: "function" names the Go package, and matching it would keep a line
for a reason the line does not show. Render and Keep share one test of
what is shown, so the two cannot drift.

The prescreen stays one-sided. The line writes a pair as "Round":49291042,
so the text is split at each "=" and every part is required on its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Git Commit a75e6cb9 Branch pull/193/head Document 8/162 ++ 32 --
tasibot nodekit
fix: wait out a rotation when follow opens the log
Follow's initial open failed on ENOENT while its polling loop waited the
same absence out in two places. The scan that produced the offset has
just read the file, so a path missing here is a rename in flight, not a
node that never started -- but the error reached the user as exactly
that, telling them to run *start* against a node that was running and
had merely rotated its log.

The open now retries at the poll interval while the context is live, and
a cancellation while waiting returns nil the way every other
cancellation does. Only absence is waited on: a permission error asked
again is still a permission error.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqPSx1wqM8rMdo4EHPa4jy
Git Commit 501b6a86 Branch pull/193/head Document 2/70 ++ 1 --
tasibot nodekit
feat: add logs command
Reading a node's logs required knowing that algod writes a logrus JSON
file, where config.json puts it, and reaching for `tail -f | jq`. This
makes it a first-class operation.

`nodekit logs` shows warnings and errors by default, with --all, --level,
--since, --filter, --lines, --follow, --json and --file. --lines counts
matching entries rather than raw lines, which is what makes the default
invocation useful on a log that is ~99% info. The rotated archives are
read as part of one history, decompressed on the way past.

The reader lives in internal/algod/logs and is independent of the CLI.
A request for a fixed number of entries walks backwards from the end of
the newest source under a 512 MiB budget; a request for every match
streams forwards from the oldest, holding nothing (a million entries at
58 MiB of RSS on a 692 MiB log, against 4.7 GiB buffered). A prescreen
decides the great majority of lines from their raw bytes, at ~10x the
throughput of parsing each one, and is fuzz-guarded against ever
rejecting a line the full filter would keep.

config.Config gains LogPaths, mirroring go-algorand's ResolveLogPaths so
that HotDataDir, LogFileDir and LogArchiveDir are honoured rather than
assuming <datadir>/node.log.

Two fixes fall out of this. IsDataDir treated only os.IsNotExist as a
missing directory, so a permission error dereferenced a nil FileInfo;
and a command that returned an error printed nothing and still exited 0,
which made every failure invisible to a shell script. main now reports
the error on stderr and exits non-zero, and cobra is silenced so it is
not printed twice. The upgrade notice moves to stderr for the same
reason: it must not land in the middle of a piped stream.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RrRQEXGWtmBskmUbiZbRi1
Git Commit 08c25931 Branch pull/193/head Document 33/4,641 ++ 6 --
tasibot nodekit
ci: exercise the logs command in the node tests
Runs `nodekit logs` against a real node on both runners, before and
after a stop, and adds internal/algod/logs to the paths that trigger the
workflow.

Two things the obvious version of this would have got wrong. `sudo`
resets the environment, so GOCOVERDIR would not reach the privileged
runs and they would contribute nothing to the coverage profile the last
step collects; `sudo -E` carries it through. And guarding the
unprivileged read with `|| echo` swallows every non-zero exit, panic
included, which is precisely the failure it was meant to catch: only the
message separates the two, so the message is what is asserted on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RrRQEXGWtmBskmUbiZbRi1
Git Commit f6439750 Branch pull/193/head Document 1/18 ++ 0 --
tasosbit nodekit
Merge bf9f72790009f84d50ba4dba769e85d04a627489 into ff027ed1650d50ae101f3ef7592a0ea1a59d94a3
Git Commit 401138e7 Branch pull/192/merge Document 21/1,714 ++ 490 --