~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/knotless

git that lives in your Atmosphere account

knotless.dev
1 collaborator

knotless 0.13.0 Latest 2 packages, 1 image

1
10

The pages say what to do on the page, not which command to run

Merged chadtmiller.com asked to merge into main from chadtmiller.com/knotless:web/no-cli-in-messages
.changeset/ejected-logs.mdnew+7−0
@@ -0,0 +1,7 @@
---
default: patch
---
# `knotless show` names the command that reads an ejected pull's logs
A pull the merge queue ejected as its checks failed shows `logs: knotless checks <url> --pull <pull> --log`, or `--queue <branch>` for a batch, after where it stands. The runner no longer writes that command into the reason it records, which the web pages show beside links to the checks that failed.
crates/knotless/src/cli.rs+5−0
@@ -1721,6 +1721,11 @@ async fn command(
let w = crate::queue::standing(ctx, &url, &i.uri).await?;
let said = w.map(|w| w.said()).unwrap_or_default();
writeln!(out, "merge queue: {} at {}\n{said}", q.state, q.created_at)?;
if let Some(logs) =
crate::queue::logs(&url.to_string(), &i.uri, i.base.as_deref(), q)
{
writeln!(out, "logs: {logs}")?;
}
}
if let Some(body) = &t.item.body {
writeln!(out, "\n{body}")?;
crates/knotless/src/queue.rs+57−1
@@ -22,7 +22,7 @@ use crate::approvals;
use crate::checks::{self, Check, Filter, Standing};
use crate::closing;
use crate::env::Ctx;
use crate::forge::{self, Entry, Item, Target};
use crate::forge::{self, Entry, Item, Queued, Target};
use crate::git::Git;
use crate::keys;
use crate::push::Update;
@@ -415,6 +415,25 @@ impl Waiting {
}
}
/// For a pull ejected as checks failed, the command that reads their
/// logs: its own runs, for a pre-flight, or the batches of the queue into
/// `base`. None for one ejected otherwise, or whose reason, as runners
/// once wrote it, names the command itself.
pub fn logs(url: &str, pull: &str, base: Option<&str>, q: &Queued) -> Option<String> {
let why = q.reason.as_deref().unwrap_or_default();
if q.state != EJECTED || q.checks.is_empty() || why.contains("`knotless checks ") {
return None;
}
let of = match base {
Some(base) if !why.starts_with("its pre-flight") => {
let branch = base.strip_prefix("refs/heads/").unwrap_or(base);
format!("--queue {branch}")
}
_ => format!("--pull {pull}"),
};
Some(format!("knotless checks {url} {of} --log"))
}
/// `n` as `1st`, `2nd`, `3rd`, `4th` and on.
pub fn ordinal(n: usize) -> String {
let suffix = match (n % 10, n % 100) {
@@ -984,4 +1003,41 @@ mod tests {
);
assert_eq!(branches(&items), ["main"]);
}
#[test]
fn ejected_logs_name_their_runs() {
let q = |reason: &str, checks: Vec<FailedCheck>| Queued {
state: EJECTED.into(),
reason: Some(reason.into()),
commit: None,
checks,
by: "did:plc:o".into(),
at: "t".into(),
created_at: "2026-01-01T00:00:00Z".into(),
};
let failed = || {
vec![FailedCheck {
workflow: "test".into(),
status: Some("failure".into()),
check: None,
}]
};
let of = |q: &Queued| logs("knotless://o/r", "at://p", Some("refs/heads/main"), q);
let flight = q("its pre-flight failed at its head h", failed());
assert_eq!(
of(&flight).as_deref(),
Some("knotless checks knotless://o/r --pull at://p --log")
);
let batch = q("its batch failed its checks at t", failed());
assert_eq!(
of(&batch).as_deref(),
Some("knotless checks knotless://o/r --queue main --log")
);
assert_eq!(of(&q("it conflicts in f", vec![])), None);
let old = q(
"x. Their logs: `knotless checks u --pull p --log`",
failed(),
);
assert_eq!(of(&old), None);
}
}
crates/knotless-run/src/queue.rs+3−7
@@ -64,7 +64,7 @@ use std::sync::{Arc, Mutex};
fn refused(err: &anyhow::Error) -> Option<String> {
let refused = XrpcError::of(err).filter(|e| e.status == 403)?;
Some(format!(
"the gateway would not land its batch: {}. A runner lands the merge queue through the gateway the owner delegates writes to: under Delegated writes in their settings on its pages, or with `knotless delegate`",
"the gateway would not land its batch: {}. A runner lands the merge queue through the gateway the owner delegates writes to: under Delegated writes in their settings",
refused.message
))
}
@@ -399,13 +399,11 @@ impl Runner {
}
self.drop_from(on.watch, line, 0);
let why = format!(
"its batch failed its checks at {}, on {} at {}: {}. Their logs: `knotless checks {} --queue {} --log`",
"its batch failed its checks at {}, on {} at {}: {}",
short(flight.stack.tip()),
on.branch,
short(&flight.stack.base),
failed.join(", "),
on.watch.remote.url,
on.branch,
);
match &flight.stack.stacked[..] {
[one] => {
@@ -565,13 +563,11 @@ impl Runner {
.map(|c| format!("{} ({})", c.record.workflow, c.record.status))
.collect();
let why = format!(
"its pre-flight failed at its head {}, on {} at {}: {}. Their logs: `knotless checks {} --pull {} --log`",
"its pre-flight failed at its head {}, on {} at {}: {}",
short(&sha),
on.branch,
short(tip),
failed.join(", "),
on.watch.remote.url,
item.uri,
);
let checks: Vec<FailedCheck> = (stands.failed.iter())
.map(|c| FailedCheck {
crates/knotless-run/tests/run.rs+14−3
@@ -3026,7 +3026,9 @@ fn a_merge_queue_lands_in_batches() {
}
let shown = q.shows(&x, "merge queue: ejected");
assert!(shown.contains("Halving the batch found it"), "{shown}");
assert!(shown.contains("`knotless checks "), "{shown}");
assert!(shown.contains("\nlogs: knotless checks "), "{shown}");
assert!(shown.contains(" --queue main --log"), "{shown}");
assert!(!shown.contains("Their logs"), "{shown}");
assert!(shown.contains(". Failed: ci (failure)"), "{shown}");
// It names the check that failed, as the runner filed it.
let listed = q.ok(&["queue", &url, "--json"]);
@@ -3182,7 +3184,10 @@ fn a_runner_named_lands_through_the_gateway() {
"{shown}"
);
assert!(shown.contains("has not delegated writes"), "{shown}");
assert!(shown.contains("`knotless delegate`"), "{shown}");
assert!(
shown.contains("under Delegated writes in their settings"),
"{shown}"
);
}
#[test]
@@ -3252,7 +3257,13 @@ fn a_merge_queue_waits_for_preflights() {
shown.contains("its pre-flight failed at its head "),
"{shown}"
);
assert!(shown.contains(&format!("--pull {p} --log`")), "{shown}");
assert!(
shown.contains(&format!(
"\nlogs: knotless checks {} --pull {p} --log",
q.url
)),
"{shown}"
);
assert!(shown.contains(". Failed: first (failure)"), "{shown}");
assert!(in_batch(&r) && !in_batch(&s) && !in_batch(&p));
crates/knotless-serve/src/web/landing.rs+1−1
@@ -262,7 +262,7 @@ pub async fn queue_pull(
) -> Result<Response> {
let (ctx, repo) = acting(&app, viewer, &headers, "queue a pull", &input.repo).await?;
if undelegated(&app, &repo) {
let why = "its merge queue lands through this server once its owner delegates writes to it, under Delegated writes in their settings or with `knotless delegate`";
let why = "its merge queue lands through this server once its owner delegates writes to it, under Delegated writes in their settings";
return Err(Shown(StatusCode::BAD_REQUEST, why.into()));
}
let queued = knotless::queue::enqueue(&ctx, &url_of(&repo), &input.pull).await;
docs/design/merge-queue.md+7−5
@@ -235,9 +235,9 @@ alpha reaches go unrun, and the batch's run tests against both and holds
the 100% gate.
An ejected pull never entered a batch. Its reason names the head, the
branch and its tip, the failed workflows, and `knotless checks <repo>
--pull <uri> --log` for their logs, and `checks` points at their
records.
branch and its tip, and the failed workflows, and `checks` points at
their records. `knotless show` adds the command that reads their logs,
`knotless checks <repo> --pull <uri> --log`.
When nobody has run a pre-flight at the head, the runner runs only those
workflows, on the pull itself, as a pull's run: the head merged with the
@@ -326,8 +326,10 @@ and those of any batch ahead of it, are cancelled. The failure says some
pull broke something, not which.
A batch of one names its culprit, so that pull is ejected at its head. The
reason names the tip, the branch and its commit, the failed workflows, and
how to read their logs, and `checks` points at their records.
reason names the tip, the branch and its commit, and the failed
workflows, and `checks` points at their records. `knotless show` adds the
command that reads their logs, `knotless checks <repo> --queue <branch>
--log`.
A larger batch's pulls become suspects, and the runner halves: the first
half of the suspects, in queue order and rounded up, runs alone as the
docs/reference/cli.md+1−1
@@ -164,7 +164,7 @@ What they print:
| `comment` | the `at://` URI of each comment it wrote, a line each: the review first, then its comments on lines |
| `resolve`, `unresolve` | `resolved`, `unresolved` |
| `list` | a line per item: `<kind>`, `<state>`, `<uri>`, `<title>`, tab-separated |
| `show` | `<state> <kind> by <author>: <title>`, then `draft` for a draft pull, `review requested: <handles>` for one that asks for reviews, then `closed by <pull>` for an issue a pull closed, `mentioned in <pull>` for each pull whose title or body names an issue, oldest first, ending `, to close it` where it names it after a closing keyword, `reverts <pull>` for a revert, `reverted by <pull>` for a merged pull whose revert merged, then for a pull each reviewer's standing review as `review: <reviewer> <verdict> at <commit>` (the first 12 characters of the head it looked at), then for an open pull in or ejected from the merge queue `merge queue: <state> at <createdAt>` and where it stands, then the body and each comment as `<author> [<verdict>]: <body>`, where a comment on lines says ` on <path>:<lines>[:old] (<uri>[, resolved])` before the colon, and an answer in a thread ` answering <uri>` |
| `show` | `<state> <kind> by <author>: <title>`, then `draft` for a draft pull, `review requested: <handles>` for one that asks for reviews, then `closed by <pull>` for an issue a pull closed, `mentioned in <pull>` for each pull whose title or body names an issue, oldest first, ending `, to close it` where it names it after a closing keyword, `reverts <pull>` for a revert, `reverted by <pull>` for a merged pull whose revert merged, then for a pull each reviewer's standing review as `review: <reviewer> <verdict> at <commit>` (the first 12 characters of the head it looked at), then for an open pull in or ejected from the merge queue `merge queue: <state> at <createdAt>` and where it stands, and for one ejected as checks failed `logs: <command>`, the `knotless checks` that reads their logs, then the body and each comment as `<author> [<verdict>]: <body>`, where a comment on lines says ` on <path>:<lines>[:old] (<uri>[, resolved])` before the colon, and an answer in a thread ` answering <uri>` |
| `merge` | the commit the branch is now at, then `deleted <branch>` and a `closed <issue>` line per issue it closed; queued instead, `<base> takes pulls through the merge queue: queued, <n> of <of>`, then where to follow it |
| `merge --queue` | `queued into <base>: <n> of <of>`, then where to follow it |
| `merge --check` | `<state>`, `<method>` and `checks <checks>`, and `reviews <passed or blocked>` when its branch asks for reviews, tab-separated, then a `missing <what>` line for each thing its reviews lack (`reviews 1 of 2`, `owners of web/: @alice or @bob`, `changes asked by @carol`; owners who are the pull's author alone add `, its author alone, whose own approval never counts, so web/ needs another owner in CODEOWNERS`), an `uncounted <why>` line for each approval that does not count (`@dan's approval does not count: only the owner's and collaborators' approvals count`), a `warning <what>` line for each CODEOWNERS line skipped, a `conflict <path>` line per file that conflicts, `deletes <branch>` for the branch it would delete, `may <what>`, `queue only` when its branch takes pulls only through the merge queue, and last why it cannot merge, in the words `merge` would refuse with, and that the caller may not merge it when it may not |
harness/shots/fixtures.json+2−2
@@ -237,7 +237,7 @@
"content-type": "application/json",
"cache-control": "no-cache"
},
"body": "{\"branches\":[{\"branch\":\"main\",\"pulls\":[{\"pull\":{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhbuzv5n3\",\"kind\":\"pull\",\"title\":\"Count bytes in files that are not UTF-8\",\"state\":\"open\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T12:31:00.000Z\",\"comments\":0},\"queuedAt\":\"2026-10-15T09:30:00.000Z\",\"said\":\"In a batch of 2 being checked\",\"batch\":0},{\"pull\":{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhc5eopws\",\"kind\":\"pull\",\"title\":\"Line the columns up with tabs\",\"state\":\"open\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T14:22:00.000Z\",\"comments\":0},\"queuedAt\":\"2026-10-16T09:30:00.000Z\",\"said\":\"In a batch of 2 being checked\",\"batch\":0},{\"pull\":{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhcffbj7l\",\"kind\":\"pull\",\"title\":\"Count words in any script\",\"state\":\"open\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T16:13:00.000Z\",\"comments\":0},\"queuedAt\":\"2026-10-17T09:30:00.000Z\",\"said\":\"Queued, 3rd of 3\"}],\"batches\":[{\"commit\":\"ee347cbe693bbc8c7f70dbb7b30bc329f34dcfa6\",\"state\":\"checking\",\"size\":2,\"checks\":[{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.check/qdjbmvovu2tnodagwluxk6jxmq.3myvwh72yvtgz.gkn5el4746pzeyvms3btnm2gqt.ee347cbe693bbc8c7f70dbb7b30bc329f34dcfa6.226488be4afaf037c2331c7480d4a48790f20eba.build\",\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"workflow\":\"build\",\"status\":\"success\",\"commit\":\"ee347cbe693bbc8c7f70dbb7b30bc329f34dcfa6\",\"startedAt\":\"2026-10-20T10:00:00Z\",\"finishedAt\":\"2026-10-20T10:01:35.000Z\",\"images\":[]},{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.check/qdjbmvovu2tnodagwluxk6jxmq.3myvwh72yvtgz.gkn5el4746pzeyvms3btnm2gqt.ee347cbe693bbc8c7f70dbb7b30bc329f34dcfa6.226488be4afaf037c2331c7480d4a48790f20eba.test\",\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"workflow\":\"test\",\"status\":\"running\",\"commit\":\"ee347cbe693bbc8c7f70dbb7b30bc329f34dcfa6\",\"startedAt\":\"2026-10-20T10:00:00Z\",\"images\":[]}]}]}],\"ejected\":[{\"pull\":{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhcnn4cic\",\"kind\":\"pull\",\"title\":\"Print a total for many files\",\"state\":\"open\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T18:04:00.000Z\",\"comments\":0},\"ejectedAt\":\"2026-10-19T15:02:00.000Z\",\"reason\":\"its batch failed its checks at 6f89484c63a3, on main at 226488be4afa: test (failure). Their logs: `knotless checks knotless://bob.test/tally --queue main --log`\",\"checks\":[{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.check/qdjbmvovu2tnodagwluxk6jxmq.3myvwh72yvtgz.gkn5el4746pzeyvms3btnm2gqt.6f89484c63a36649168e3d77a1e631f695469b74.226488be4afaf037c2331c7480d4a48790f20eba.test\",\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"workflow\":\"test\",\"status\":\"failure\",\"commit\":\"6f89484c63a36649168e3d77a1e631f695469b74\",\"startedAt\":\"2026-10-19T15:00:00Z\",\"finishedAt\":\"2026-10-19T15:01:35.000Z\",\"images\":[]}]}],\"landed\":[{\"branch\":\"main\",\"commit\":\"226488be4afaf037c2331c7480d4a48790f20eba\",\"landedAt\":\"2026-10-19T09:01:35.000Z\",\"pulls\":[{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhbacgmx6\",\"kind\":\"pull\",\"title\":\"Show usage on --help\",\"state\":\"merged\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T08:49:00.000Z\",\"comments\":0}]}]}"
"body": "{\"branches\":[{\"branch\":\"main\",\"pulls\":[{\"pull\":{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhbuzv5n3\",\"kind\":\"pull\",\"title\":\"Count bytes in files that are not UTF-8\",\"state\":\"open\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T12:31:00.000Z\",\"comments\":0},\"queuedAt\":\"2026-10-15T09:30:00.000Z\",\"said\":\"In a batch of 2 being checked\",\"batch\":0},{\"pull\":{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhc5eopws\",\"kind\":\"pull\",\"title\":\"Line the columns up with tabs\",\"state\":\"open\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T14:22:00.000Z\",\"comments\":0},\"queuedAt\":\"2026-10-16T09:30:00.000Z\",\"said\":\"In a batch of 2 being checked\",\"batch\":0},{\"pull\":{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhcffbj7l\",\"kind\":\"pull\",\"title\":\"Count words in any script\",\"state\":\"open\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T16:13:00.000Z\",\"comments\":0},\"queuedAt\":\"2026-10-17T09:30:00.000Z\",\"said\":\"Queued, 3rd of 3\"}],\"batches\":[{\"commit\":\"ee347cbe693bbc8c7f70dbb7b30bc329f34dcfa6\",\"state\":\"checking\",\"size\":2,\"checks\":[{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.check/qdjbmvovu2tnodagwluxk6jxmq.3myvwh72yvtgz.gkn5el4746pzeyvms3btnm2gqt.ee347cbe693bbc8c7f70dbb7b30bc329f34dcfa6.226488be4afaf037c2331c7480d4a48790f20eba.build\",\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"workflow\":\"build\",\"status\":\"success\",\"commit\":\"ee347cbe693bbc8c7f70dbb7b30bc329f34dcfa6\",\"startedAt\":\"2026-10-20T10:00:00Z\",\"finishedAt\":\"2026-10-20T10:01:35.000Z\",\"images\":[]},{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.check/qdjbmvovu2tnodagwluxk6jxmq.3myvwh72yvtgz.gkn5el4746pzeyvms3btnm2gqt.ee347cbe693bbc8c7f70dbb7b30bc329f34dcfa6.226488be4afaf037c2331c7480d4a48790f20eba.test\",\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"workflow\":\"test\",\"status\":\"running\",\"commit\":\"ee347cbe693bbc8c7f70dbb7b30bc329f34dcfa6\",\"startedAt\":\"2026-10-20T10:00:00Z\",\"images\":[]}]}]}],\"ejected\":[{\"pull\":{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhcnn4cic\",\"kind\":\"pull\",\"title\":\"Print a total for many files\",\"state\":\"open\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T18:04:00.000Z\",\"comments\":0},\"ejectedAt\":\"2026-10-19T15:02:00.000Z\",\"reason\":\"its batch failed its checks at 6f89484c63a3, on main at 226488be4afa: test (failure)\",\"checks\":[{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.check/qdjbmvovu2tnodagwluxk6jxmq.3myvwh72yvtgz.gkn5el4746pzeyvms3btnm2gqt.6f89484c63a36649168e3d77a1e631f695469b74.226488be4afaf037c2331c7480d4a48790f20eba.test\",\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"workflow\":\"test\",\"status\":\"failure\",\"commit\":\"6f89484c63a36649168e3d77a1e631f695469b74\",\"startedAt\":\"2026-10-19T15:00:00Z\",\"finishedAt\":\"2026-10-19T15:01:35.000Z\",\"images\":[]}]}],\"landed\":[{\"branch\":\"main\",\"commit\":\"226488be4afaf037c2331c7480d4a48790f20eba\",\"landedAt\":\"2026-10-19T09:01:35.000Z\",\"pulls\":[{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhbacgmx6\",\"kind\":\"pull\",\"title\":\"Show usage on --help\",\"state\":\"merged\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T08:49:00.000Z\",\"comments\":0}]}]}"
},
"/xrpc/dev.knotless.getRepo?repo=at%3A%2F%2Falice.test%2Fdev.knotless.repo%2Fscratch": {
"status": 200,
@@ -341,7 +341,7 @@
"content-type": "application/json",
"cache-control": "no-cache"
},
"body": "{\"item\":{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhcnn4cic\",\"kind\":\"pull\",\"title\":\"Print a total for many files\",\"state\":\"open\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T18:04:00.000Z\",\"comments\":0},\"base\":\"main\",\"head\":{\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"branch\":\"totals\"},\"comments\":[],\"closes\":[],\"queue\":{\"state\":\"ejected\",\"reason\":\"its batch failed its checks at 6f89484c63a3, on main at 226488be4afa: test (failure). Their logs: `knotless checks knotless://bob.test/tally --queue main --log`\",\"createdAt\":\"2026-10-19T15:02:00.000Z\",\"said\":\"Ejected: its batch failed its checks at 6f89484c63a3, on main at 226488be4afa: test (failure). Their logs: `knotless checks knotless://bob.test/tally --queue main --log`. Failed: test (failure)\",\"checks\":[{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.check/qdjbmvovu2tnodagwluxk6jxmq.3myvwh72yvtgz.gkn5el4746pzeyvms3btnm2gqt.6f89484c63a36649168e3d77a1e631f695469b74.226488be4afaf037c2331c7480d4a48790f20eba.test\",\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"workflow\":\"test\",\"status\":\"failure\",\"commit\":\"6f89484c63a36649168e3d77a1e631f695469b74\",\"startedAt\":\"2026-10-19T15:00:00Z\",\"finishedAt\":\"2026-10-19T15:01:35.000Z\",\"images\":[]}]},\"checks\":[],\"minutes\":[{\"runner\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"minutes\":2140,\"allowance\":2000,\"freeing\":[{\"at\":\"2026-11-02T00:00:00Z\",\"minutes\":400}],\"updatedAt\":\"2026-10-25T17:52:00Z\"}],\"changes\":{\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"commits\":[{\"sha\":\"6f89484c63a36649168e3d77a1e631f695469b74\",\"parents\":[{\"sha\":\"226488be4afaf037c2331c7480d4a48790f20eba\"}],\"author\":{\"name\":\"Bob Tanaka\",\"actor\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"}},\"coauthors\":[],\"date\":\"2026-10-11T12:00:00Z\",\"committedAt\":\"2026-10-11T12:00:00Z\",\"message\":\"Print a total for many files\"}],\"files\":[{\"old\":\"docs/totals.md\",\"new\":\"docs/totals.md\",\"status\":\"added\",\"added\":1,\"removed\":0,\"rows\":[],\"rowCount\":2,\"hunkCount\":1}]},\"card\":\"/bob.test/tally/pull/did:plc:55j6e4wxzlzndxm4nov2lz7x/3myvwhcnn4cic/card.png?v=0bde049a81b5f11e\"}"
"body": "{\"item\":{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhcnn4cic\",\"kind\":\"pull\",\"title\":\"Print a total for many files\",\"state\":\"open\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T18:04:00.000Z\",\"comments\":0},\"base\":\"main\",\"head\":{\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"branch\":\"totals\"},\"comments\":[],\"closes\":[],\"queue\":{\"state\":\"ejected\",\"reason\":\"its batch failed its checks at 6f89484c63a3, on main at 226488be4afa: test (failure)\",\"createdAt\":\"2026-10-19T15:02:00.000Z\",\"said\":\"Ejected: its batch failed its checks at 6f89484c63a3, on main at 226488be4afa: test (failure). Failed: test (failure)\",\"checks\":[{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.check/qdjbmvovu2tnodagwluxk6jxmq.3myvwh72yvtgz.gkn5el4746pzeyvms3btnm2gqt.6f89484c63a36649168e3d77a1e631f695469b74.226488be4afaf037c2331c7480d4a48790f20eba.test\",\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"workflow\":\"test\",\"status\":\"failure\",\"commit\":\"6f89484c63a36649168e3d77a1e631f695469b74\",\"startedAt\":\"2026-10-19T15:00:00Z\",\"finishedAt\":\"2026-10-19T15:01:35.000Z\",\"images\":[]}]},\"checks\":[],\"minutes\":[{\"runner\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"minutes\":2140,\"allowance\":2000,\"freeing\":[{\"at\":\"2026-11-02T00:00:00Z\",\"minutes\":400}],\"updatedAt\":\"2026-10-25T17:52:00Z\"}],\"changes\":{\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"commits\":[{\"sha\":\"6f89484c63a36649168e3d77a1e631f695469b74\",\"parents\":[{\"sha\":\"226488be4afaf037c2331c7480d4a48790f20eba\"}],\"author\":{\"name\":\"Bob Tanaka\",\"actor\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"}},\"coauthors\":[],\"date\":\"2026-10-11T12:00:00Z\",\"committedAt\":\"2026-10-11T12:00:00Z\",\"message\":\"Print a total for many files\"}],\"files\":[{\"old\":\"docs/totals.md\",\"new\":\"docs/totals.md\",\"status\":\"added\",\"added\":1,\"removed\":0,\"rows\":[],\"rowCount\":2,\"hunkCount\":1}]},\"card\":\"/bob.test/tally/pull/did:plc:55j6e4wxzlzndxm4nov2lz7x/3myvwhcnn4cic/card.png?v=0bde049a81b5f11e\"}"
},
"/xrpc/dev.knotless.getTree?repo=at%3A%2F%2Falice.test%2Fdev.knotless.repo%2Fscratch": {
"status": 200,
harness/shots/world.mjs+1−1
@@ -697,7 +697,7 @@ export async function create() {
}
await write('dev.knotless.queue', {
repo, subject: ejected, state: 'ejected', commit: tip('totals'), checks: failed,
reason: `its batch failed its checks at ${tip('totals').slice(0, 12)}, on main at ${landed.slice(0, 12)}: test (failure). Their logs: \`knotless checks knotless://bob.test/tally --queue main --log\``,
reason: `its batch failed its checks at ${tip('totals').slice(0, 12)}, on main at ${landed.slice(0, 12)}: test (failure)`,
createdAt: '2026-10-19T15:02:00.000Z',
})
}
web/src/lib/organisms/AskedFor.svelte+2−4
web/src/lib/organisms/EjectedList.svelte+2−2
web/src/lib/organisms/EmailSettings.svelte+0−3
web/src/lib/organisms/SettingsAccess.svelte+1−1
web/src/lib/organisms/ThemeEditor.svelte+0−1
web/src/lib/organisms/WriteSettings.svelte+0−3
web/src/lib/format.ts+7−0
web/src/routes/[authority]/[repo]/[kind=item]/[did]/[rkey]/+page.svelte+2−2
web/src/routes/[authority]/[repo]/checks/+page.svelte+2−2
web/src/routes/[authority]/[repo]/queue/+page.svelte+1−1