rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 heartwood6c4656b4f3755fff7ff9468629c0158333ce1736
{
"request": "trigger",
"version": 1,
"event_type": "patch",
"repository": {
"id": "rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5",
"name": "heartwood",
"description": "Radicle Heartwood Protocol & Stack",
"private": false,
"default_branch": "master",
"delegates": [
"did:key:z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT",
"did:key:z6MktaNvN1KVFMkSRAiN4qK5yvX1zuEEaseeX5sffhzPZRZW",
"did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"did:key:z6MkgFq6z5fkF2hioLLSNu1zP2qEL1aHXHZzGH1FLFGAnBGz",
"did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz"
]
},
"action": "Created",
"patch": {
"id": "1b9e7b8d991c99d9981a91b65230e12736cb30ea",
"author": {
"id": "did:key:z6MkrnXJWPndzPBxpBUaE3L3BnMeWpaQdT1V1FvkoCPFSFS3",
"alias": "yorgos-laptop"
},
"title": "radicle: Accept `<transport>::rad://` URL convention",
"state": {
"status": "open",
"conflicts": []
},
"before": "22b2871f64ecf34a22d32add0dd59a0c7c96ad10",
"after": "6c4656b4f3755fff7ff9468629c0158333ce1736",
"commits": [
"6c4656b4f3755fff7ff9468629c0158333ce1736"
],
"target": "22b2871f64ecf34a22d32add0dd59a0c7c96ad10",
"labels": [],
"assignees": [],
"revisions": [
{
"id": "1b9e7b8d991c99d9981a91b65230e12736cb30ea",
"author": {
"id": "did:key:z6MkrnXJWPndzPBxpBUaE3L3BnMeWpaQdT1V1FvkoCPFSFS3",
"alias": "yorgos-laptop"
},
"description": "Extend the local transport URL parser to accept the standard\nGit <transport>::<address> format (gitremote-helpers(7)) for\nremote helper composition. This allows a single-remote workflow\n(e.g. `age::rad://`) instead of requiring separate `rad` and\n`age` remotes, avoiding accidental cleartext pushes.\n\nNote: `Url::from_str` accepts the prefixed form but `Display`\nalways outputs canonical `rad://` \u2014 the transport prefix is not\npart of the Radicle URL identity, so the round-trip is lossy by\ndesign.\n\nSigned-off-by: Yorgos Saslis <yorgos.work@proton.me>",
"base": "22b2871f64ecf34a22d32add0dd59a0c7c96ad10",
"oid": "6c4656b4f3755fff7ff9468629c0158333ce1736",
"timestamp": 1775576288
}
]
}
}
{
"response": "triggered",
"run_id": {
"id": "bb5b02c2-7bd3-493a-96b4-a32db1856c5e"
},
"info_url": "https://cci.rad.levitte.org//bb5b02c2-7bd3-493a-96b4-a32db1856c5e.html"
}
Started at: 2026-04-07 17:38:23.418305+02:00
Commands:
$ rad clone rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 .
✓ Creating checkout in ./...
✓ Remote cloudhead@z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT added
✓ Remote-tracking branch cloudhead@z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT/master created for z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT
✓ Remote cloudhead@z6MktaNvN1KVFMkSRAiN4qK5yvX1zuEEaseeX5sffhzPZRZW added
✓ Remote-tracking branch cloudhead@z6MktaNvN1KVFMkSRAiN4qK5yvX1zuEEaseeX5sffhzPZRZW/master created for z6MktaNvN1KVFMkSRAiN4qK5yvX1zuEEaseeX5sffhzPZRZW
✓ Remote fintohaps@z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM added
✓ Remote-tracking branch fintohaps@z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM/master created for z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM
✓ Remote erikli@z6MkgFq6z5fkF2hioLLSNu1zP2qEL1aHXHZzGH1FLFGAnBGz added
✓ Remote-tracking branch erikli@z6MkgFq6z5fkF2hioLLSNu1zP2qEL1aHXHZzGH1FLFGAnBGz/master created for z6MkgFq6z5fkF2hioLLSNu1zP2qEL1aHXHZzGH1FLFGAnBGz
✓ Remote lorenz@z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz added
✓ Remote-tracking branch lorenz@z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz/master created for z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz
✓ Repository successfully cloned under /opt/radcis/ci.rad.levitte.org/cci/state/bb5b02c2-7bd3-493a-96b4-a32db1856c5e/w/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 154 issues · 41 patches │
╰────────────────────────────────────╯
Run `cd ./.` to go to the repository directory.
Exit code: 0
$ rad patch checkout 1b9e7b8d991c99d9981a91b65230e12736cb30ea
✓ Switched to branch patch/1b9e7b8 at revision 1b9e7b8
✓ Branch patch/1b9e7b8 setup to track rad/patches/1b9e7b8d991c99d9981a91b65230e12736cb30ea
Exit code: 0
$ git config advice.detachedHead false
Exit code: 0
$ git checkout 6c4656b4f3755fff7ff9468629c0158333ce1736
HEAD is now at 6c4656b4 radicle: Accept `<transport>::rad://` URL convention
Exit code: 0
$ rad patch show 1b9e7b8d991c99d9981a91b65230e12736cb30ea -p
╭──────────────────────────────────────────────────────────────────────────────╮
│ Title radicle: Accept `<transport>::rad://` URL convention │
│ Patch 1b9e7b8d991c99d9981a91b65230e12736cb30ea │
│ Author yorgos-laptop z6MkrnX…CPFSFS3 │
│ Head 6c4656b4f3755fff7ff9468629c0158333ce1736 │
│ Base 22b2871f64ecf34a22d32add0dd59a0c7c96ad10 │
│ Branches patch/1b9e7b8 │
│ Commits ahead 1, behind 0 │
│ Status open │
│ │
│ Extend the local transport URL parser to accept the standard │
│ Git <transport>::<address> format (gitremote-helpers(7)) for │
│ remote helper composition. This allows a single-remote workflow │
│ (e.g. `age::rad://`) instead of requiring separate `rad` and │
│ `age` remotes, avoiding accidental cleartext pushes. │
│ │
│ Note: `Url::from_str` accepts the prefixed form but `Display` │
│ always outputs canonical `rad://` — the transport prefix is not │
│ part of the Radicle URL identity, so the round-trip is lossy by │
│ design. │
│ │
│ Signed-off-by: Yorgos Saslis <yorgos.work@proton.me> │
├──────────────────────────────────────────────────────────────────────────────┤
│ 6c4656b radicle: Accept `<transport>::rad://` URL convention │
├──────────────────────────────────────────────────────────────────────────────┤
│ ● Revision 1b9e7b8 @ 6c4656b by yorgos-laptop z6MkrnX…CPFSFS3 17 seconds ago │
╰──────────────────────────────────────────────────────────────────────────────╯
commit 6c4656b4f3755fff7ff9468629c0158333ce1736
Author: Yorgos Saslis <yorgos.work@proton.me>
Date: Fri Apr 3 15:28:31 2026 +0300
radicle: Accept `<transport>::rad://` URL convention
Extend the local transport URL parser to accept the standard
Git <transport>::<address> format (gitremote-helpers(7)) for
remote helper composition. This allows a single-remote workflow
(e.g. `age::rad://`) instead of requiring separate `rad` and
`age` remotes, avoiding accidental cleartext pushes.
Note: `Url::from_str` accepts the prefixed form but `Display`
always outputs canonical `rad://` — the transport prefix is not
part of the Radicle URL identity, so the round-trip is lossy by
design.
Signed-off-by: Yorgos Saslis <yorgos.work@proton.me>
diff --git a/crates/radicle/src/storage/git/transport/local/url.rs b/crates/radicle/src/storage/git/transport/local/url.rs
index ba07c1486..ad41c96df 100644
--- a/crates/radicle/src/storage/git/transport/local/url.rs
+++ b/crates/radicle/src/storage/git/transport/local/url.rs
@@ -18,7 +18,7 @@ pub enum UrlError {
#[error("invalid url format: expected `rad://<repo>[/<namespace>]`")]
InvalidFormat,
/// Unsupported URL scheme.
- #[error("unsupported scheme: expected `rad://`")]
+ #[error("unsupported scheme: expected `rad://` or `<transport>::rad://`")]
UnsupportedScheme,
/// Invalid repository identifier.
#[error("repo: {0}")]
@@ -79,6 +79,11 @@ impl FromStr for Url {
fn from_str(s: &str) -> Result<Self, Self::Err> {
let rest = s
.strip_prefix("rad://")
+ .or_else(|| {
+ s.rsplit_once("::rad://")
+ .filter(|(prefix, _)| !prefix.is_empty())
+ .map(|(_, rest)| rest)
+ })
.ok_or(UrlError::UnsupportedScheme)?;
let components = rest.split('/').collect::<Vec<_>>();
@@ -123,6 +128,27 @@ mod test {
assert_eq!(url.repo, repo);
assert_eq!(url.namespace, Some(namespace));
+ // <transport>::rad:// (gitremote-helpers(7) convention)
+ let url = format!("age::rad://{}", repo.canonical());
+ let url = Url::from_str(&url).unwrap();
+
+ assert_eq!(url.repo, repo);
+ assert_eq!(url.namespace, None);
+
+ let url = format!("age::rad://{}/{namespace}", repo.canonical());
+ let url = Url::from_str(&url).unwrap();
+
+ assert_eq!(url.repo, repo);
+ assert_eq!(url.namespace, Some(namespace));
+
+ // Negative cases for <transport>::rad:// convention.
+ assert!(format!("::rad://{}", repo.canonical()).parse::<Url>().is_err());
+ assert!(
+ format!("age::heartwood://{}", repo.canonical())
+ .parse::<Url>()
+ .is_err()
+ );
+
assert!(
format!("heartwood://{}", repo.canonical())
.parse::<Url>()
Exit code: 0
shell: 'export RUSTDOCFLAGS=''-D warnings'' cargo --version rustc --version cargo fmt --check cargo clippy --all-targets --workspace -- --deny warnings cargo build --all-targets --workspace cargo doc --workspace --no-deps --all-features cargo test --workspace --no-fail-fast '
Commands:
$ podman run --name bb5b02c2-7bd3-493a-96b4-a32db1856c5e -v /opt/radcis/ci.rad.levitte.org/cci/state/bb5b02c2-7bd3-493a-96b4-a32db1856c5e/s:/bb5b02c2-7bd3-493a-96b4-a32db1856c5e/s:ro -v /opt/radcis/ci.rad.levitte.org/cci/state/bb5b02c2-7bd3-493a-96b4-a32db1856c5e/w:/bb5b02c2-7bd3-493a-96b4-a32db1856c5e/w -w /bb5b02c2-7bd3-493a-96b4-a32db1856c5e/w -v /opt/radcis/ci.rad.levitte.org/.radicle:/${id}/.radicle:ro -e RAD_HOME=/${id}/.radicle rust:trixie bash /bb5b02c2-7bd3-493a-96b4-a32db1856c5e/s/script.sh
+ export 'RUSTDOCFLAGS=-D warnings'
+ RUSTDOCFLAGS='-D warnings'
+ cargo --version
info: syncing channel updates for '1.90-x86_64-unknown-linux-gnu'
info: latest update on 2025-09-18, rust version 1.90.0 (1159e78c4 2025-09-14)
info: downloading component 'cargo'
info: downloading component 'clippy'
info: downloading component 'rust-docs'
info: downloading component 'rust-src'
info: downloading component 'rust-std'
info: downloading component 'rustc'
info: downloading component 'rustfmt'
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
info: installing component 'rust-src'
info: installing component 'rust-std'
info: installing component 'rustc'
info: installing component 'rustfmt'
cargo 1.90.0 (840b83a10 2025-07-30)
+ rustc --version
rustc 1.90.0 (1159e78c4 2025-09-14)
+ cargo fmt --check
Diff in /bb5b02c2-7bd3-493a-96b4-a32db1856c5e/w/crates/radicle/src/storage/git/transport/local/url.rs:142:
assert_eq!(url.namespace, Some(namespace));
// Negative cases for <transport>::rad:// convention.
- assert!(format!("::rad://{}", repo.canonical()).parse::<Url>().is_err());
+ assert!(
+ format!("::rad://{}", repo.canonical())
+ .parse::<Url>()
+ .is_err()
+ );
assert!(
format!("age::heartwood://{}", repo.canonical())
.parse::<Url>()
Exit code: 1
{
"response": "finished",
"result": "failure"
}