chadtmiller.com/knotless
git that lives in your Atmosphere account
run log shows what a check prints while it runs
MergedThis adds dev.knotless.getRunOutput, a long-polling query that serves a running check's kept output. It also adds knotless run log with --follow, and the MCP tool run_log. check_line is now shared with run list.
I read the serve handler, checks.rs (newest, again, output) and run_log in cli.rs, and found nothing that must change.
- Auth:
named_checkruns before anything is served, so the read check matchesgetCheck. - Wait: the handler caps
waitat 25 s and re-reads the run after the wait. - Cursor: the
<startedAt>/<byte>cursor is handled sensibly. A cursor from another run, or an empty one, reads from the start. - Client loop: the
outputandrun_logloops terminate onended, on a filed record, or whenwaitruns out. Re-reading the record on each quiet 25 s round is what ends a follow whose runner never filed.
I did not open the tests (mcp.rs, run.rs), the MCP wiring in knotless-mcp/src/lib.rs, the lexicon or the docs files, and I did not read the checks. The tests, lexicon lint and coverage are for CI to enforce.
One small thing, not worth holding the pull for: when the 1 MB tail is cut, or a byte cursor lands inside a multibyte character, from_utf8_lossy may print a replacement character. You already list the cut with no gap marker among the shortcomings.
I read what changed since the approved commit, and the approval stands.
- Private reads:
membernow takes the reader from the newspace::reader, whichjoinedalso uses. That gives the same session choice asjoined, so the owner's session is used when the owner is logged in. The refactor looks behaviour-preserving. - Follow loop: it now sleeps for zero time and re-reads the record on every quiet round, not only after
ended. This matches the intent. The record is read again each quiet 25 s round, which is what ends a follow whose runner never filed. - Ended before it was followed: a missing log now prints nothing instead of failing. That is correct.
- MCP test: the wait of 27 s checks that a wait longer than the appview's 25 s cap is asked again for the rest. That is a useful case.
I found nothing that must change.
10 passed, 1 skipped
From the command line or MCP you can now read what a check prints while it runs:
knotless run log <url> --pull <uri> --workflow testprints what it printed so far and the step it is on, and--followkeeps printing until it ends; the MCP toolrun_loggives the same, withsinceandwait. A new appview query,dev.knotless.getRunOutput, serves it to whoever may read the check. The serve handler and the client loop are the parts worth a look.Claude Opus 5.5 · medium
What changes
dev.knotless.getRunOutput(new lexicon, served besidegetCheck): what the appview holds of a running check, as/_live/checksends it to the pages, but as one JSON answer:textaftercursor(all that is kept without one),cursor,stepsthat ended,going,ended. With nothing new and the run going it waits up towaitseconds (at most 25) for the runner to send more, holding a follower meanwhile, so the runner sends every second. It is checked asgetCheckand/_live/checkare (named_check): anyone for a public repository, members for a private one. The cursor is<startedAt>/<byte>, so a cursor from the last run of a check that was run again at the same commit reads the new run from its start rather than skipping into it.knotless run log <url> [--ref] [--commit] [--pull] [--queue] [--workflow] [--follow]: the newest check therun listfilters find (checks of several workflows are an error naming them). Running: what it printed so far, then the check's line asrun listprints it, with\tstep <name>for the step going. Ended: its log, then its line.--followwaits up to a minute for a check to show, prints output as it comes (asking 25 s at a time), and once the run's steps are over waits for the record to be filed and prints its line. A private repository's output is read with a delegation token for its space.MCP
run_log: the same arguments, plussince(a cursor it gave) andwait(at most 300 s, for the check to show and then for more output). Running:{text, cursor, steps, going, ended, check},textits last 8000 bytes; finished:{text, check},textthe end of its log. InREADS, so read-only servers offer it.run list's line is now made by one function (check_line) both commands share. Docs:cli.md,mcp.md,actions.md,api.md(Live),serve.md(Live),runner.md.Why
run list --logandrun_listread only the check record, which has no log until the run ends, so an owner or an agent watching CI could not see which step a running check was on or what it had printed. The pages already had it through/_live/check, a server-sent event stream; a query is simpler for a CLI and the only thing an MCP tool can use. Long-polling withwaitkeeps it close to live without a tight loop, and counting the waiting client as a follower keeps the runner's one-second cadence.How it was checked
agents_follow_a_run(knotless-mcp): an appview, a running and a finished check filed as records, output sent withputRunOutputas the runner: several workflows refused, output so far with the step going, nothing more after the cursor (waited), then only what came after andended, the finished check's log, none found after waiting, the queue filter, a bad commit, and no appview.a_run_shows_as_it_runs(knotless-run): a real run held on its first step:run logshows the masked output andstep first;--followprints the second step and ends onsuccess; afterwards the log; every flag parsed; a bad flag refused.a_private_repository_runs_in_its_space(knotless-run, spaces alpha): a private repository's held run read through the delegation token, then followed to its end.Shortcomings
--followon a run whose runner dies without filing its record keeps asking until interrupted (it rereads the record each quiet 25 s, so it does stop once anything files the check).Review
Start at
crates/knotless-serve/src/web/live.rs(get_run_output,had), thencrates/knotless/src/checks.rs(newest,again,output) andrun_logincli.rs.