| 2026-11-05 |
Whatever is necessary, statically compiled (the operator: "we use whatever is necessary, including c, zig, modern rust, no backwards compat. our users & clients do not care about building the tool, they care about downloading & using it. the only rule is: everything must be statically compiled"; given to the podcrates session, and applied to podssh: "change it and avoid self sabotage"). C, zig and the newest stable Rust (edition 2024) may be used in each crate, the library crates included; no crate keeps a minimum Rust version for others' sake. The one rule of the build: each released binary is statically compiled. The checks that enforced the old rules (the gate's CC=/nonexistent and its steps at a declared minimum Rust) are retired by T-685, so that none blocks the crates of podcrates. |
| 2026-11-05 |
No compatibility with the released binaries, for now (the operator: "we do not care about legacy clients or published binaries, we only did beta and only my agents tested them, so we are free to break and migrate / we can think about legacy and compatibility later"). The move to PodUtils (above) and the crates of podcrates may change what a released binary needs: the signer that podssh update accepts, the feed it reads, the pairing's AppID and each other name of the wire. No bridge release, no test against a released binary. Compatibility is a later decision. |
| 2026-11-05 |
The repository moves to PodUtils/podssh, and the shared crates go to PodUtils/podcrates (the operator: "repo will migrate to: https://github.com/PodUtils/podssh / and many of the crates will be shared with other projects here, will go into https://github.com/PodUtils/podcrates"). podnet (formerly cfrs) moves to PodUtils/podnet. The names of the repository in the code and the documents follow the move (T-683); the crates of podcrates and their waves are Q101 (T-684). crates.io holds podssh, podnet and podvm for PodUtils (0.0.0, 2026-11-05). |
| 2026-11-05 |
podbox is renamed podvm (the operator), and podvm is reserved on crates.io. The text of podssh names it podvm; dated records and the paths of the old project keep podbox. |
| 2026-11-04 |
Q100: the testers' issues #126 to #148, the defect first (the operator). Each of GitHub #126 to #148 (Nemo-010, 2026-11-04) becomes an entry now. #126, a refusal of the relay role that names the address a target resolved to, is a P1 defect and goes first; the 22 features go by their priority, beside M10 and the entries of Q99 (Q85). A fork of policy that one meets is decided and listed for the operator to confirm (Q86). |
| 2026-11-04 |
Q99: osewa's network and connectivity, at parity and beyond (the operator: "i want us to have everything they have network/connectivity wise + beat/exceed them", after an honest study first). Two studies, of the code of rjyo/osewa and of its paper (https://osewa.dev/paper), compare it with podssh; each gap where osewa has a network or connectivity feature that podssh lacks becomes an entry, with a way to exceed it. Those entries go right after the testers' defects, ahead of the rest of M10's order. Their live tests may reach osewa's public servers with short runs, where the servers' terms allow, as Q65 and Q91 allow. |
| 2026-11-04 |
Q93 to Q97, confirmed (the operator, as each was recommended). Q93: the TURN road (T-621) opens the data connection that RFC 6062 needs, to the same server, only when the user names one with --turn, an exception of rule 2 of AGENTS.md. Q94: short live tests against smee.io, hostc, Tunnelmole and Sandhole's public server (T-623, T-624), with throwaway channels and tunnels given up at the end, once each research entry has read the service's terms. Q95: TURN is a road of the user's own server with a file of credentials, and the one-POST HTTP form is a form of the relay role's HTTP road, not forms of --forwarder (T-621, T-622). Q96: with PODSSH_RELAY_LAYER unset, proxy and ssh read the offer of the resumable layer of a relay host that the user named, once a day, 1 s at most, before the session's connection (T-173). Q97: before the race of a session on the default list, podssh asks the /relays.json of each relay member that the user's net names, once a day for each member, 5 s each and 8 s in all, and takes a list only by the member's own key (T-617). AGENTS.md, rule 2, names the exceptions of Q93, Q96 and Q97. |
| 2026-11-03 |
Q90: the calls of Q86, each ruled (the operator, asked one by one). Confirmed as written in each entry's ## Decision: T-503 (each hop of -J checked by the direct road's rule), T-476 (a special file skipped with exit 0), T-505 (the algorithms that known_hosts records go first; russh's lists stay the base, ssh-rsa among them), T-470 (cp --delta's one remote command where there is no copy-data), T-471 (sftp's ls -n), T-563 (a strike only for a session that the host's side cut), T-560 (a reset inside WARP's tunnel is an answer), T-587 (proxy node:// is operator's pipe), T-590 (doctor dials the local sshd once), T-592 (a renewal keeps the tokens, with no cap; each failure 403), T-593 (a cache only where the uid owns it), T-594 (STATUS's release marks), T-595 (--carrier) and T-531 (OpenTunnel at the node when --opentunnel names it, and unasked for a node with no other road, as Q76 rules, which the operator kept when asked again; a late fallback at the client). Changed: T-574's binary ships a short list of public proxy nodes as a fallback when no list can be fetched, each node checked live before each release and dropped when it fails, the filters of T-574 kept (a node that fronts another party's name, or under a school's, a church's or a government's suffix, is never one); T-586's resumption gets a way off, --no-resume; T-589's commands take --timeout and PODSSH_TIMEOUT as other names of --ConnectTimeout, a bound on the open alone; T-596's exit over a carrier is taken by ssh, proxy and pipe too, each with --exit beside --carrier. |
| 2026-11-03 |
Q89: podssh ranks relay hosts unasked only with RelayRank auto (the operator). By default podssh probes no relay host that the user did not ask it to: podssh relay rank measures on demand, and the default list uses a rank kept from it and the timings of each session's own race. With RelayRank auto, a session may rank in its background, at most once a day for each egress, 8 hosts, one probe each 10 s at most, each probe with ?dial=lazy so that no target is dialed: a bounded exception to rule 2 (one outbound connection), which the user asks for by the setting. |
| 2026-11-02 |
Q87: v1.0.1 and the next beta go out unattended (the operator). Once the testers' defects are merged, v1.0.1 is tagged and published as soon as its checks pass (the gate green on its commit, the release workflow run by hand on it, the local checks of its binary, as for v1.0.0), and its published files are checked; then v1.1.0-beta.1 for the work of M10, at most one beta a day. |
| 2026-11-02 |
Q86: overnight, a session decides a fork of policy and records it (the operator). Where a draft or an entry meets a question that changes a policy of this file, the session makes the defensible call, writes it in the entry's ## Decision and as a row "for the operator to confirm" in TODO/PROGRESS.md, and goes on; a call that the operator overturns becomes an entry of repair. |
| 2026-11-02 |
Q85: the testers' features go too (the operator: "you may begin working on the rest of the items you were waiting for the go ahead"). The features of the testers' reports (GitHub #83 to #111, the bundle and the sweep) and the entries that Q74 to Q82 filed are done in the work order's turn, beside M10, after the defects. |
| 2026-11-02 |
Q84: the testers' defects go now (the operator). The defects among the entries of the testers (GitHub #83 to #106, the bundle and the sweep of 2026-11-02) are done now, P1 first, then P2, and go into v1.0.1 with the checks of a release; their features followed by Q85. M10's entries go on beside them. |
| 2026-11-02 |
Q83: the experience of an agent and of a human comes first (the operator: "podssh will be used by agents and humans, and better ux we can offer, better they like and prefer podssh over writing their own wrappers or using another tool"). The calls of the triage of #83 to #105 stay: a patched copy of russh (T-460), cp's files several at once by default (T-474), job --no-tmux (T-481), and health --preflight's brief listen at the far server (T-482); and each call of the triage of the bundle (T-501, T-503, T-504, T-506, T-507, T-510, T-512). |
| 2026-11-02 |
Q82: roads over UDP where UDP passes (the operator). Hysteria 2, TUIC and MASQUE over HTTP/3 become client roads, tried only where a probe found that UDP passes, after the roads over TCP. |
| 2026-11-02 |
Q81: a node with no iroh road keeps its pkarr record at n0's server too (the operator; T-445). A node of the quick tunnel or of the DERP road alone puts its record, which names only public names, at n0's pkarr server unasked; this widens Q57. |
| 2026-11-02 |
Q80: the operator passes the sweep's defect of cfrs on (the operator). A session acts on this repository alone, as Q26 left podbox's pin to the operator. |
| 2026-11-02 |
Q79: the repository names only the hosts that podssh uses (the operator). Of a sweep of public services, the repository and the binary name each host that podssh uses or tries, with its source, and keep the rest as classes and counts. A domain that looks taken over (a school's, a church's, a shop's, or a name made to look like one) is never used and never published. |
| 2026-11-02 |
Q78: each service is a last fallback, whatever its terms (the operator: "use all as last resort"). A public service that carries bytes is tried after podssh's own roads and after the services that allow the use, even where its own terms forbid it (file hosts such as 0x0.st, hosts such as serv00), and the lists of public proxy nodes (VLESS, VMess, Trojan) are a last fallback; podssh tells the user that the target goes to a third party that nobody vouches for, and SSH and the end-to-end channel keep the bytes private. The session's own note at the ruling: a service run by one volunteer that asks not to be used so can close for each user. |
| 2026-11-02 |
Q77: use the fallbacks that exist (the operator: "doing nothing or not using available fallbacks/relays/features is just making podssh useless in the exact scenarios it is being built for"). Overlay networks of other projects (NetBird, Firezone, ZeroTier, Tox, Yggdrasil, croc, HyperDHT, Cabal, Hyphanet) each get a research entry, for a host with TCP to 443 alone or where UDP passes; each that passes becomes a road, tried unasked after podssh's own. Nostr's ephemeral events and the trackers become meeting points of the pairing, in the same order. |
| 2026-11-02 |
Q76: OpenTunnel, unasked, for a node with no other road (the operator; T-530 to T-532). One tunnel for each node, off with PODSSH_OPENTUNNEL=no; short live tests from GitHub Actions and the session's own host. |
| 2026-11-02 |
Q75: the public Wisp servers that are made for it, as fallbacks (the operator). Beside Mercury Workshop's demo server, podssh tries the public Wisp servers that their operators run for that use, after the other forwarders; never a domain that looks taken over (Q79). Short live tests. |
| 2026-11-02 |
Q74: Tailscale's main DERP fleet, after tailcat's (the operator). A client that is not Tailscale's may try the main fleet's servers after tailcat's, once a short test shows that they let podssh in; the test may reach them, with short runs. |
| 2026-11-02 |
Q73: doctor runs its sections at once (the operator: "all at once, but make it configurable"). Each section starts at once by default; a flag and a setting limit how many run together, down to one at a time (T-511). |
| 2026-11-02 |
Q72: scp and sftp take --timeout (the operator; T-508). An optional bound there, PODSSH_TIMEOUT as its default, never required; cp and mv keep theirs. |
| 2026-11-02 |
Q71: the keywords of a restriction apply (the operator; T-505). Ciphers, MACs, KexAlgorithms, HostKeyAlgorithms, PubkeyAcceptedAlgorithms (and its old name), CASignatureAlgorithms and RequiredRSASize filter what podssh offers, in OpenSSH's +, - and ^ forms; a list that leaves nothing is refused before anything connects. The other keywords of a restriction that the user set get one note at the default level. |
| 2026-11-02 |
Q70: a file's ProxyCommand for each host stays refused (the operator; T-502). Exit 78, as T-043 decided, and the refusal names -o ProxyCommand=none. |
| 2026-11-02 |
Q69: the fallback to other products' forwarders follows only a failure where no relay host answered about the target (the operator; T-509, GitHub #106). It stays on by default (Q56, Q65), after a dial, TLS, a timeout, an upgrade, the relay's own 5xx or a refused token; never after the relay's 502 about the target or its refusal of a target by policy. Its line says what each forwarder gets; doctor marks it as not measured. |
| 2026-11-02 |
Q68: the login name from the user database, last, and PODSSH_USER (the operator; T-500). PODSSH_USER names the login, then USER, LOGNAME and USERNAME, then the effective uid's entry of /etc/passwd (read as a file; getpwuid_r on macOS); a host with no entry goes on with a refusal that names -l, PODSSH_USER and USER. |
| 2026-11-02 |
Q67: XHTTP over HTTP/1.1 may open its second connection (the operator; T-456). Another exception of rule 2 of AGENTS.md: tried only after HTTP/2 was refused, to the same server, and named by doctor --vless. |
| 2026-11-02 |
Q63: podssh grows into a mesh of its user's own nodes, at parity with Tailscale, and the relay becomes a role of podssh itself (the operator; mesh.md, milestone M10). The operator's words: "having our own relay seems just passing the problem to someone else ... perhaps it is time, we went full on tailscale-like parity? introduce, exit mode, serve, funnel, etc; then it would make sense to have the relay in podssh itself". A node may lend its egress to its owner's other ends (exit mode, and routes to the networks that it reaches), share a local service with them (Tailscale's serve), and publish one at a public https:// name (funnel). The relay's role is podssh's own: a node may serve the relay's contract to its owner's ends, on a listener that the user asks for (Q1) or behind its quick tunnel with no listener, and may relay between two of its owner's ends. The entries that waited for the relay's operator are podssh's work, done in podssh's own relay role and on its client. tcp.ssh.relay.ajam.dev stays the default relay; a change of that deployment stays an item of relay-operator.md, and no entry waits for one. |
| 2026-11-02 |
Q64: Cloudflare's terms are accepted (the operator: "for cloudflare, i have accepted the terms & risks, do not relitigate again"). The operator accepted Cloudflare's terms and their risks, for the relay, the quick tunnel, WARP and each other use of Cloudflare. A session does not raise them again, and writes no question and no risk of them. WARP's registration still asks its user (PODSSH_WARP_ACCEPT_TERMS=yes, Q59), as that acceptance is the user's own. |
| 2026-11-02 |
Q65: public services with no account, tried unasked (the operator; carriers.md). The magic-wormhole transit relay of Least Authority (a carrier of a pair on the reverse road), the public magic-wormhole mailboxes (pairing by a short code, T-086), Piping Server (ppng.io, for a proxy that refuses the WebSocket upgrade) and the Wisp demo server (a forwarder's word after the three of Q56): each may be tried when the user did not ask, as Q50 allows n0's relays. So may each other public service that fits these needs, once its terms and limits are read and written in carriers.md. Their live tests are short runs, from GitHub Actions and from a session's own host. |
| 2026-11-02 |
Q66: standard protocols, so that a server that a user runs is a road (the operator). podssh speaks, as a client, HTTP CONNECT to a second proxy over TLS (HTTP/1.1 and HTTP/2), VLESS over a WebSocket and over XHTTP, and wstunnel's protocol; and the others that the expansion of Q63 needs. Each is tested against the server of its own project. |
| 2026-11-02 |
v1.0.0 now, and the expansion after it (the operator: "yes release it, i will have clients test it for bugs while you work on the expansion"). v1.0.0 is published from 35a0d19, where the whole release check passed (T-251); the testers test it while sessions build the expansion of Q63 (milestone M10). A defect found in it goes into v1.0.1. |
| 2026-11-01 |
Q60: the Tor road's live test may reach the public Tor network (the operator; T-353). Short runs, from GitHub Actions and from a session's own host: one bootstrap of Arti, a few MiB of directory, then one stream to github.com:22, as Q52 allowed for WARP. The private network on the loopback (chutney) stays the test against software that podssh did not write. |
| 2026-11-01 |
Q61: this binary started again at the user's request is no helper, and a detached run may ask systemd for a scope (the operator; T-380, T-381). --detach, ssh -f and session start start podssh again; rule 3 of AGENTS.md says that this is no helper. On Linux a detached podssh may ask the user's systemd, natively over D-Bus on its socket of this host, for a scope of its own once it is ready, so that logind's KillUserProcesses at a logout does not end it, as tmux does; a failure is a line, and PODSSH_SYSTEMD=no turns it off. |
| 2026-11-01 |
Q62: the quick tunnel's node keeps a second connection to the edge (the operator; T-414). A fourth exception of rule 2 of AGENTS.md, for podssh node --quick-tunnel alone: a second standing connection to Cloudflare's edge, of the index 1, to the edge's other region first, so that a region in trouble or a GOAWAY leaves one connection standing. No new host and no listener. |
| 2026-11-01 |
v1.0.0 may be published unattended (the operator). Once each workable entry is done (not those of the relay's operator, those that Q32 holds, nor T-328 and T-376, which wait for the operator's exports and throwaway cloud accounts and stay known gaps that the notes name), and the whole release check (T-251) passes on its commit, a session tags and publishes v1.0.0 (T-250) and checks the published files; v1.0.0-beta.4 goes out first, after 2026-11-02 01:44 UTC. Q32's holds stay, and stay out of v1.0.0. |
| 2026-10-31 |
Q49: relays outside ssh.relay.ajam.dev, when they are compatible (the operator; T-346). A binary may use a relay of another deployment and another domain that speaks the relay's contract. Relays that their users bring (the relay template of #60) and a podssh that updates itself (T-375) make a signed list of deployments less pressing. A token goes only to the deployment that minted it. |
| 2026-10-31 |
Q50: the iroh road in the release binaries, with n0's public relays and Tailscale's tailcat fleet as an automatic fallback (the operator; T-348, T-349, T-350). A node and a client with no pair may try them when the user did not ask, as a fallback of podssh node and ssh node://; the operator does not ask n0 or Tailscale first. The iroh road brings its C into the binary, as russh does. |
| 2026-10-31 |
Q51: roads through a cloud's own tunnel, GCP IAP and AWS EC2 Instance Connect Endpoint (the operator; T-351). They need the user's cloud account and a token file. EICE's presigned URL may carry its credential, an exception to "credentials never in URLs" for that URL alone, which podssh never prints or logs. They are built against stand-ins; their live run waits for the operator's throwaway accounts (T-376). |
| 2026-10-31 |
Q52: WARP's MASQUE road and Tor through Arti (the operator; T-352, T-353). Tor is a feature crate, podssh-tor, with the C of arti-client, as podssh-iroh is one. WARP's live test may run in GitHub Actions and on a session's own host. |
| 2026-10-31 |
Q53: doctor listens once on each socket that it binds (the operator; T-358). It calls listen() with a backlog of 1 on 127.0.0.1 port 0 and on a Unix socket of mode 0600 in the temporary directory, accepts nothing, and closes each at once, so that a host that allows bind() and refuses listen() reads as one that cannot listen; never with PODSSH_LISTEN=no. Rule 2 of AGENTS.md, SECURITY.md and docs/architecture.md say so with T-358. |
| 2026-10-31 |
Q56: the public WebSocket forwarders of #59, built in and as a fallback (the operator; T-369, T-370). The binary may carry templates of the three that #59 measured (Mudlet's Worker web-proxy.mudlet.org, its older copy, and ssheasy's ssheasy.com/p), each selected by a word, and may try them when each relay host fails, as Q50 allowed for n0's and tailcat's relays. Live tests against them run in GitHub Actions only, short. No relay token goes to a forwarder. |
| 2026-10-31 |
Q57: a node's record of pkarr at n0's server, by default (the operator; T-350). A node whose home relays are n0's publishes a signed record of its home relay to n0's public pkarr server, at its start, when its home changes, and each 5 minutes, unasked, as Q50 lets it use n0's relays; a client whose ticket's relay does not reach the node asks for that record. PODSSH_IROH_PKARR=off turns it off, or names another server. |
| 2026-10-31 |
Q58: tailcat's DERP fleet, in tests and as the unasked fallback (the operator; T-349, T-379). A session may run short live tests (exchanges of 64 KiB or so) against Tailscale's public tailcat DERP fleet, from its own host and from GitHub Actions. A node with no pair serves the DERP road unasked, as Q50's fallback; PODSSH_DERP=no turns it off. |
| 2026-10-31 |
Q59: WARP in the release binaries once its live run passes (the operator; T-352). The live test may register a throwaway WARP device with Cloudflare, from GitHub Actions and from a session's own host (Q52); a registration needs PODSSH_WARP_ACCEPT_TERMS=yes, the user's acceptance of Cloudflare's terms. Once warp-live.yml passes, the feature warp joins RELEASE_FEATURES, as the iroh road did. |
| 2026-10-31 |
podssh update (the operator; T-375). An explicit command that fetches the release for its own target through the proxy, checks the signed SHA256SUMS and the binary's sum (T-210, T-211), and replaces itself at once; it never runs on its own. |
| 2026-10-31 |
Q48: podssh is a client first, and no legacy holds it back (the operator; T-333). Its clients run in the most restricted hosts there are: they cannot build podssh, and some cannot even download it, so it is uploaded or copied to them. podssh therefore never gives up a capability, performance or the health of the project for a legacy constraint that no one outside this project and its CI cares about, such as an older minimum Rust for an embedder: the library crates' minimum rises with what their dependencies need (Rust 1.89 for aes-gcm 0.11). |
| 2026-10-30 |
Q47: podssh makes a quick tunnel itself (T-300). HTTP/2 only, in a module of podssh-relay, with the three roots that cloudflared adds beside podssh's own; the relay's road to the edge where TCP 7844 is not reached; its live tests in GitHub Actions only, as T-299's (Q43). The operator reads Cloudflare's terms as allowing a client other than cloudflared. |
| 2026-10-30 |
Q44: a second connection for the split HTTP road (T-319). Where a probe finds that a proxy refuses the WebSocket upgrade, a session may run as one streamed GET down and a POST for each chunk up, two connections at once, on this road only; the serial form (one POST after another, one connection) is its fallback. It is the third exception of rule 2 of AGENTS.md (section 5). The relay's endpoints for it wait for the relay's operator. |
| 2026-10-30 |
Q45: a cleartext road on port 443, last (T-326). Tried only after the TLS road failed, with a relay token minted for that one session and never cached, as each hop can read the header; doctor names the road that answered. podssh's own relay role serves the endpoint (Q63, T-326). |
| 2026-10-30 |
Betas before v1.0.0 (the operator). Betas v1.0.0-beta.N are prereleases from main, so that the testers in real sandboxes, whose reports are the issues, test the current code. A beta is tagged when the gate (build.yml, its job all) passed on its commit, the release workflow passed by hand on it, and the local checks of the binary passed (T-309). The next beta follows a batch of repairs that the testers should test again, at most one a day. A beta does not wait for T-210, T-211, T-218 or T-251; its notes say what it does not have. A defect found in a beta goes into the next beta, and a tag never moves. v1.0.0 stays the final release (T-250), after the checks of T-251. |
| 2026-10-09 |
A node that lost its socket connects again on 409 (Q31; T-261). It tries again with the backoff until the resume deadline of 10 minutes, so the sessions that the resumable layer keeps on it go on; a first registration still exits on 409. Rule 6 of reverse.md gives both cases. |
| 2026-10-09 |
Cloudflare: a client first (Q33; T-264). podssh reaches a service that cloudflared or Cloudflare Access publishes, over a WebSocket through HTTPS_PROXY. A quick tunnel that podssh makes itself follows (Q47, 2026-10-30; T-300). |
| 2026-10-09 |
A node has a plain mode (Q34; T-263). podssh node --plain carries each session's bytes as they are, with no resumable layer, for a client that does not speak it; the layer stays the default. |
| 2026-10-08 |
Sessions run unattended, toward v1.0.0 (Q13 to Q28). A session works until the operator interrupts it, or until each entry of TODO/ is done except the entries that the operator holds, each test passes, and the release of the work (v1.0.0, then the release of milestone M10, Q63) is published and verified from end to end. It asks nothing and waits for nothing: it follows this page and each entry's Decision; else it makes a defensible call and records it in the entry. It corrects a row of this page whose facts its own work changed, with the old text under Superseded; a change of policy waits for the operator, and the entry is blocked with a question. It may use the live relay, railway.new, GitHub's SSH endpoint, the two tailnet hosts, the Podman box and GitHub Actions, with throwaway keys. AGENTS.md (section 2) tells how. |
| 2026-10-08 |
v1.0.0, the final release, at the end (Q13 to Q18, Q29, Q30). Betas come before it (the row of 2026-10-30). v1.0.0 comes after every other entry except the relay's (T-250), and is tagged once: the check from end to end with no human (T-251) runs before the tag on the gate's artifact and a local build, and after the tag on the published files; a defect found then goes into v1.0.1. The check: checksums and Sigstore signatures; the commands of the README; doctor; proxy and ssh through the live relay; the reverse road; serve with vi, less and Ctrl-C in the box; cp of 200 MiB each way; a session that survives a killed relay connection; pipe. It ships macOS x86_64 and aarch64, Linux x86_64, aarch64, armv7 and riscv64 (static: x86_64 and aarch64 in the gate's image, armv7 with Alpine's own rust, riscv64 cross-compiled with cargo-zigbuild), and Windows x86_64 and aarch64, each built and run in CI (T-218). T-004 and T-005 are measured in the box; a run in the operator's real sandboxes follows the release. Confirmed 2026-10-09 (Q37): an unattended session tags and publishes it after the checks of T-251. |
| 2026-10-08 |
A GitHub issue closes after its work (Q20). When each entry of an issue is done, the session comments with the commits and a short summary, and closes it. An issue that also has entries that wait for the relay's operator closes when its other entries are done; the comment names the entries that wait. Nothing is posted before the work is done. |
| 2026-10-08 |
Chat on the roads first, and IRC as a second transport (Q21). podssh chat runs on the roads between two podssh ends, end to end encrypted (T-088), after M6 (T-099). The repaired IRC client (T-091 to T-098) is a second transport for public networks (T-252). |
| 2026-10-08 |
podssh pipe exec:CMD is allowed (Q22; T-174). The user names the program, as with SSH_ASKPASS; the rule against helper processes is about podssh's own shims. |
| 2026-10-08 |
Several relay sessions for one copy, when the user asks (Q23; T-141). A flag sets the number; the default is 1. Several channels in one connection come first. |
| 2026-10-08 |
AF_UNIX and named pipes on Windows (Q24; T-176). podssh pipe probes each at run time, and refuses with the reason where it fails. |
| 2026-10-08 |
podbox: the facade here, the pin later (Q26). The blocking facade of podssh-relay for podbox is built and tested in this repository (T-081), with Rust 1.88 if it needs that; podbox follows. Pinning it in podbox is an operator action after the release: no session changes another repository (T-085). |
| 2026-10-08 |
A tty that podssh makes where /dev/ptmx is missing (Q11; T-248). Two routes, each after a probe at run time: a new devpts instance in a user namespace, which is a real pty (the operator: the boxes have no user namespaces), and a tty in user space: podssh serve answers its own child's tty system calls (seccomp user notification, or ptrace). The second is not a shim: it puts no code into the child, and it works with static binaries. LD_PRELOAD stays forbidden. The exit criterion of M5 keeps vi, less and Ctrl-C. |
| 2026-10-08 |
A local listener when the user asks for it and a probe allows it (Q1). -L and -D (T-038), connection sharing (T-039), serve --listen (T-124), a local session daemon (T-186) and podssh agent (T-034) may listen when the user asks for them and a probe at run time shows that the bind works. The default address is loopback, or an AF_UNIX socket. The user can set the address, and can turn listening off. Where the probe fails, podssh refuses with a clear message, as now. Nothing listens until those entries are done. |
| 2026-10-08 |
Three ways to try the relay hosts (Q10; T-220, GitHub #30), selected by a flag: a staggered start (the default: the next host starts when the first has not opened within a short delay), all hosts at once, or one host at a time with a shorter connect limit for a host that has a successor. The first success in the order of preference wins, and the other attempts close at once. So podssh can have more than one outbound connection for a short time; the session uses one. |
| 2026-10-08 |
Agent forwarding (-A) is in the scope of podssh (Q2; T-036), off by default as in OpenSSH. |
| 2026-10-08 |
The SSH agent and the credential helper are parts of podssh (Q5), not separate binaries: podssh agent (T-034), a credential helper (T-228), and hardware keys (T-229). |
| 2026-10-08 |
No road depends on a program of another project (Q3: cloudflared, mosh-server). podssh makes their useful functions itself where that makes sense: a Cloudflare quick tunnel, which trycloudflare.com makes scriptable (T-170), and the functions of Mosh that M6 and T-160 do not give (T-171). |
| 2026-10-08 |
The relay's tokens, and trust end to end (Q4, Q9). M4 keeps the relay's tokens, in a header or a 0600 file and never in a URL; the trust is end to end: the host key of the node and the allowlist of keys (T-087, T-107). Since Q63 (2026-11-02) the relay is also a role of podssh itself. |
| 2026-10-08 |
podssh is a CLI (Q6). No GUI, no TUI, no emoji. The requests for a menu-bar app and for colored command blocks (reports in GitHub #24 and #25) are closed by this ruling, with no entry. The triage of an error by an AI is a hook of the CLI: podssh gives its report to a program that the user names (T-195). |
| 2026-10-08 |
Devices and workloads are streams (Q7). A serial: address for podssh pipe (T-181); USB/IP as a tested recipe over podssh pipe, with no driver in podssh (T-182); a queue of detached remote jobs, with no GPU logic (T-197). |
| 2026-10-08 |
The surface for agents comes after M3 and before M4 (Q8): T-049, T-050, T-012, T-052 and T-051 (GitHub #9 to #13). |
| 2026-10-08 |
The way to a finished podssh (design.md). Milestone M3 comes first: relay failover, liveness, a fallback for no proxy and no DNS, doctor, keygen, and a run in a real sandbox. Then M4 (podssh-relay, the reverse road, podbox), M5 (podssh serve), and M6 (sessions that survive a drop). M6 builds two resumable layers, each the fallback of the other: podssh's own layer over the WebSocket relay, and iroh. |
| 2026-10-08 |
iroh's relays are configurable, for connections between two podssh binaries. The default is n0's public relays until the operator runs an iroh relay on the operator's own Cloudflare account. Then the operator's relay is first and n0's relays are the fallback. |
| 2026-10-08 |
The SSH client is russh with the aws-lc-rs backend, in the default build. The podssh binary links aws-lc. |
| 2026-10-08 |
The repository is public. The git history was replaced by one commit (the old history is in a local bundle), and the old CI runs were deleted. Verified milestones are pushed without a question. |
| 2026-10-08 |
Milestone 1 gives a usable default. The relay tcp.ssh.relay.ajam.dev is compiled in, and the user can override it. Mozilla's root store (webpki-roots) is the last trust fallback, after an explicit CA file and the system bundle. |
| 2026-10-08 |
Tailscale is a feature that the user must select. podssh ts is the ts cargo feature, because the fork uses most of the build time, the memory and the binary size. |
| 2026-10-05 |
Do not assume that the client has a privilege, a tool or a setup. The operator's words: "NEVER ASSUME OUR CLIENT HAS ANY PRIVS, TOOLS, SETUP." podssh probes what the host gives at runtime. What a host gives changes between sessions and images. |
| 2026-10-05 |
Tailscale support (podssh ts, alias tailscale). "Vanilla" means a TUN device. The automatic chain is tun, then socks, then tcp, then relay. podssh ssh does not use the tailnet automatically in the first release. A wait for the relay's allowlist is used only on request; the default is to fail fast. A client-side DERP home pin is in the fork. |
| 2026-10-05 |
Exit codes: 64 for a usage error, 78 for a configuration error (sysexits). |
| 2026-10-04 |
IRC safety: podssh never executes something that it receives over chat, and never gets a peer's files on its own. |
| 2026-10-01 |
No LD_PRELOAD, and no shims in other processes. A preload shim fails silently against a static binary and looks like an authentication failure. podssh supplies in its own process what the host does not have. |
| 2026-10-01 |
The terminal must be usable after the connection. When no part gives echo, editing and history (no pty on either side, and no line discipline on the server), podssh supplies the line discipline. A server with no pty can have its own line discipline (podbox has one), so three inputs select the mode. See terminal.md. |
| 2026-10-01 |
IRC is necessary. Two users in constrained hosts must be able to chat and share files. podssh supplies the protocol and the relay carries the bytes, as for SSH. Open items: the client sends plain text to public IRC servers, and the topology with no third-party server has no design yet. |
| 2026-10-01, 2026-10-08 |
Tokens (Q12 and T-057 on 2026-10-08): one machine has one cached token for each relay deployment. The hosts of one deployment share it, and podssh never sends a token to a deployment that did not mint it (T-057). The cache is in the first usable directory of: a directory that the user names (T-243), the user's cache directory, the user's runtime directory, the temporary directory, the working directory. The temporary directory is TMPDIR; when it is not set, podssh resolves one at run time, and only then tries the fixed fallbacks (/tmp, /dev/shm). podssh probes each directory, and skips one that does not exist or refuses a write. The mode is 0600. If chmod fails, podssh continues. |
| 2026-10-01 |
Command line: podssh with no subcommand never connects. chat is the IRC subcommand. The code generates the man page. |