-
v0.5.0
StableAll checks were successfulrelease / prepare (push) Successful in 4srelease / build (catthehacker/ubuntu:act-22.04, linux-x86_64, docker, x86_64-unknown-linux-gnu) (push) Successful in 2m20srelease / build (darwin-aarch64, macos-15, aarch64-apple-darwin) (push) Successful in 3m55srelease / build (darwin-x86_64, macos-15, x86_64-apple-darwin) (push) Successful in 3m44srelease / publish (push) Successful in 20srelease / guard (push) Has been skippedci / check (push) Successful in 11m6sci / live-e2e (push) Successful in 2m8sci / coverage (push) Successful in 2m30sreleased this
2026-09-13 18:32:57 +00:00 | 3 commits to main since this releaseAdded
fj stack shiprestacks the rest of the stack after a squash land. A
squash merge replaces the item's commits, so every branch above it was left
parented on history the trunk no longer had: the PRs above showed the landed
diff a second time and the stack had to be repaired by hand. Ship now replays
the remainder onto the new trunk tip with a single
git rebase --onto <new trunk> <landed tip> <top branch> --update-refs(the
plumbingstack absorbalready used), force-pushes each branch with an
explicit--force-with-lease, and re-syncs the PR bases. A conflicting
replay rolls the rebase back and force-pushes nothing, then ship stops with
the commands to finish by hand. The PR directly above the landed item is
still re-pointed at the trunk, because the base branch it was targeting has
merged away and leaving it aimed at a deleted branch would be worse than the
no-restack end state. Merge-commit lands are unaffected, since the original
commits stay reachable.--no-restackopts out. The replay needs git 2.38 or
newer.fj stack shippre-flights the stack's shape in git. Before merging
anything it verifies that each unmerged branch descends from the one below
it, that the lowest one contains the trunk's fetched tip, and that each
local branch tip matches the head of the PR that would merge. A stale or
non-linear stack is refused up front with a "restack, thenfj stack sync"
message rather than failing mid-merge with the bottom already landed.fj stack shipre-validates between merges. After an item lands, the
next one is re-checked (still open, still mergeable, still approved, still at
the head fj most recently put there) before it is merged. If the ground
shifted, ship stops cleanly, reports what already landed, and leaves the rest
for a re-run. Checks that ship itself re-armed by force-pushing a restacked
branch are not re-gated. A forge still serving the pre-restack head is a
lagging read rather than a shift, so ship re-reads a few times; if it never
catches up, that is a stop in its own right, since every field on that
snapshot describes a shape the restack has already replaced.stack ship --jsonnow reports the branches restacked after each item
(restacked), andstack review --jsoncarries each PR's head sha
(head_sha). Both fields are additive.fj run logsprints Actions job logs. The singularfj run logstays as
an alias, so existing invocations keep working. A personal access token is
enough for log reads on this Forgejo build; the auth docs no longer claim a
browser session cookie is required. A session is still needed for the
cookie-gated web routes,run watch,run rerun, andrun cancel.fj instanceslists the Fjord Commons.fjread only
/me/forge-instances, which by design excludes the Commons, so there was no
instance id to address and no way to reach it. The Commons is now read
alongside your own instances and routed through the platform gateway, with no
personal access token anywhere: the gateway mints the forge token server-side
and nothing minted reaches the client. A Commons you have not joined is
withheld frominstances usebut still named infj instances, on a dimmed
line, so "not joined" does not read as "no such forge".- Host-scoped
FJ_TOKEN_<HOST>andFJ_SESSION_<HOST>. The host suffix is
Hfollowed by the uppercase hex of the host bytes, sorasterhub.comis
FJ_TOKEN_H7261737465726875622E636F6D. The encoding is injective, so
foo-bar.comandfoo.bar.comcannot collide onto one variable, and CI can
set a credential for one host without enumerating the rest. fj pr merge --style fast-forward-only. Forgejo's fast-forward-only
merge style is accepted both as an explicit--styleand as a repository's
default_merge_style, which previously failed as unsupported. A PR that is
not a fast-forward onto its base is reported as such, with the rebase to do,
instead of a raw forge error.
Changed
fj pr mergefollows the repository's default merge style. An omitted
--styleresolved tomergeregardless of how the repository was
configured, so merging from the CLI could produce a merge commit on a repo
whose web UI squashes. It now reads the repository'sdefault_merge_style,
and fails explicitly if that cannot be read or is not a style fj supports
rather than falling back to a guess. Scripts that relied on the implicit
mergeneed--style mergespelled out. Merge-style is also reported in
push-rejection diagnostics, so a signed-commit protection failure is no
longer misread as something else.- A generic
FJ_TOKENis no longer sent to a host it was not meant for.
It used to be picked up for any host. It is now accepted for the default or
current host, including when you spell that same host with--host, and
ignored when--hostnames a different configured host. Use
FJ_TOKEN_<HOST>for that case.FJ_SESSIONfollows the same rule. - The default Fjord platform URL is
https://fjord.sh, replacing the old
preview URL. An HTML response on a platform call now names--platform-url
instead of surfacing an opaque JSON decode error. - Global
--hostreaches the auth subcommands.fj auth status,token,
refresh,session,logout, andsetup-gitnow honor it, so
fj auth status --host Xreports hostXalone.
Fixed
-
cargo test --allnow passes on macOS. Four defects kept it from
finishing there, and each one was a test reading the developer's real machine
instead of its own scratch state.ProjectDirsignoresXDG_CONFIG_HOMEon
macOS, so tests that thought they had redirected the config directory were
loading the realhosts.toml; when that named a host whose token lives in the
login keychain, the lookup then blocked forever in securityd, because a
keychain item's ACL names the binaries allowed to read it and a freshly built
test binary is not one of them.FJ_CONFIG_DIRandFJ_NO_KEYCHAINgive
those tests, and any scripted run that wants its own state, a way to say so.
The editor test hardcoded/bin/false, which exists on Linux but not on
macOS, so it asserted on a spawn failure rather than the nonzero exit it was
written for. And the loopback sign-in test drove real sockets on a paused
clock, which cannot work: tokio's auto-advance does not wait for socket
readiness, so it jumped the clock past the five-minute callback deadline while
the silent connection was still in the accept queue. That one now runs on the
real clock with the request-line bound shortened for the test; the shipped
ten-second bound is unchanged. -
The pager no longer swallows short, write, and machine output. The
decision to page was made per top-level command family, so every subcommand
underrepo,issue,pr,release,label,milestone,tag,run,
search,status, andapiwas piped intoless, including one-line write
confirmations (pr merge,issue close,repo star,tag create, ...),
fixed status blocks (fj status,fj pr status), short reads (tag view,
milestone view,repo topics), and every--jsonand--webinvocation.
Destructive commands that prompt for confirmation (repo delete,
release delete,tag delete) were prompting on a stdout already redirected
into the pager. Paging is now decided per subcommand by
cli::paging::should_page, which pages only genuinely long read-only views:
list and view commands,pr diff/commits/files/checks,
run log,search, andfj apiGET dumps.--no-pager,FJ_NO_PAGER,
FJ_PAGER/PAGER, and the non-TTY check are unchanged. -
fj pr mergeno longer discards the pull request description. With no
--message, fj sent an empty merge-commit body, and Forgejo does not fall
back to the description the way its web merge dialog does. Every pull request
merged through the CLI therefore landed with its body gone, silently: the
merge succeeded and the commit looked normal. The message now defaults to the
pull request body. An explicit--messagestill wins, and--message ""
still produces a deliberately empty body. -
Ctrl+C works when fj is blocked in a synchronous call. SIGINT was raced
against the command future, so it only took effect while that future was
still pollable. Several paths parked the thread inside a blocking syscall for
an unbounded time, and because tokio had already replaced SIGINT's default
disposition the signal did not terminate the process either: Ctrl+C was not
slow, it was inert. The two confirmed states were an OS keychain read waiting
on an authorization dialog it cannot present (over ssh, in CI, from a git
hook, which is what deadlocked the pre-push hook) and the synchronous stdin
reads behind--body -,--input -,--with-token, and the interactive
Fjord Account prompts. A backstop now gives the graceful path 250 ms to
unwind and then exits 130. A child that owns the terminal's foreground
process group still gets to decide what SIGINT means, solessabsorbing it
is unaffected. -
Ctrl+C exits during an OIDC sign-in.
fj auth loginwaited for the
loopback redirect on a blocking task, so an interrupted sign-in printed
error: interruptedand left the process alive. The wait is now async and
actually cancellable. A stalled connection no longer wedges the sign-in
either: a socket that connected and sent nothing (a browser preconnect, a
port scanner) blocked the accept loop forever and the real redirect behind it
was never served. Request lines are now bounded at 10s, and the overall 300s
budget also covers connection handling. -
FJ_TOKENno longer blocksfj auth login --fjord. The variable was
bound to--tokenby clap, so having one set forced the token flow even when
--fjordwas spelled out. Explicit--fjordnow wins.FJ_SESSIONwas
bound tofj auth session --cookiethe same way, where it conflicted with
--clear; that binding is gone too. -
fj pr checkshandles a head with no reported checks. Forgejo returns
statuses: nullthere, which failed to decode. It now prints
no checks reported on <sha>and exits 0;--jsonstill emits a normal
combined-status object withstatuses: []. -
fj run view --logreads logs on Forgejo 16. Logs are resolved through
the run-jobs and job-log APIs, and displayed run numbers are mapped to API
run ids first, including for runs past the first page.FJ_TOKENprecedence
is preserved, so an env-token-only environment does not touch an unreadable
token store. A Forgejo 15.x host, which does not expose the
token-authenticated job log API, is now reported as such by version rather
than as a parse error. -
fj run view --logno longer downloads every task in the repository on
Forgejo 16. The native job-log path fetched the workflow task list first
and discarded it, and on 16 that endpoint ignores itsrunfilter: about
3 MB and 30 seconds on every log read against a busy repository. The task
list is now loaded only by the Loki fallback, which is the one path that
needs it, and that fallback also decodes theworkflow_runsenvelope 16
returns instead of aborting on an unexpected response shape. -
fj pr listandfj pr viewdecode Forgejo's system actors. Forgejo
uses negative sentinel user ids (-1for Ghost,-2forforgejo-actions),
and the sharedUsermodel readidas unsigned, so any repository with a
system actor as reviewer, assignee, poster, or author failed the whole decode
withinvalid value: integer -2, expected u64. The field is signed now,
which covers every actor position at once. -
A failed list decode names where it failed. List responses are decoded
throughserde_path_to_error, so the message carries the endpoint and the
JSON field path (for exampleGET /api/v1/repos/o/r/pulls?...and
field [0].id) instead of only the type error. -
Tag releases publish their assets, and the generated Homebrew formula
installs. Thev0.4.1release was published with zero assets, so
brew install fjserved 0.3.0 from 22 to 30 July: one build leg failed, and
re-running could not recover because artifact keys are scoped by run id, so
the legs that had already uploaded hit "artifact already exists" and the
publish job never ran. The assets were uploaded on 30 July by the recovery
dispatch these changes made possible, and the tap has served 0.4.1 since.
Artifacts are overwritten on a re-run now, and a release can be re-run by
workflow_dispatchwith ataginput, from a branch carrying a fix, without
moving the tag. The formula could not have installed even if publish had run:
it didcd fj-<version>-<target>, but Homebrew has already entered the
tarball's top-level directory and that directory keeps the tag'svprefix,
which the formula's version drops, sobrew installended in
Errno::ENOENT: chdir_path. Three failures that used to pass quietly now
stop the run: a missing matrix leg renderingsha256 "", an asset glob
matching nothing, and a missing CHANGELOG section for the tag (release
bodies were always empty, and are now taken from that section). A dispatched
tag also reaches the shell as data rather than as a substituted expression,
must exist in the repository's tags, and is checked out asrefs/tags/<tag>
with the resolved sha asserted in every job, so a branch sharing a
version-shaped name cannot be built and published as if it were the tag.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
5 downloads
-
v0.4.1
StableSome checks failedci / check (push) Successful in 10m25sci / coverage (push) Successful in 1m41sci / live-e2e (push) Successful in 1m58srelease / build (catthehacker/ubuntu:act-22.04, linux-x86_64, docker, x86_64-unknown-linux-gnu) (push) Failing after 1m44srelease / build (darwin-aarch64, macos-15, aarch64-apple-darwin) (push) Successful in 3m49srelease / build (darwin-x86_64, macos-15, x86_64-apple-darwin) (push) Failing after 3m40srelease / publish (push) Has been skippedreleased this
2026-07-22 20:19:49 +00:00 | 38 commits to main since this releaseFixed
- Silent Fjord Account session refresh across every command. An expired
Fjord access token is now renewed transparently at the shared HTTP client: on
a 401 from a host signed with a Fjord bearer, fj refreshes via the stored
refresh token and retries the request once, sofj api,fj pr,fj issue,
fj repo, and the Actions log routes no longer fail with "sign in again" once
the short-lived token lapses. Previously onlyfj instancesandfj auth
refreshed. PAT hosts (whose tokens are not refreshable) and the case with no
usable refresh token surface the original 401 unchanged, and the refresh is
bounded to one attempt per request so a persistently-401 endpoint cannot loop.
Downloads
-
Source code (ZIP)
2 downloads
-
Source code (TAR.GZ)
6 downloads
- Silent Fjord Account session refresh across every command. An expired
-
v0.3.0
StableAll checks were successfulrelease / build (catthehacker/ubuntu:act-22.04, linux-x86_64, docker, x86_64-unknown-linux-gnu) (push) Successful in 2m35srelease / build (darwin-aarch64, macos-15, aarch64-apple-darwin) (push) Successful in 3m54srelease / build (darwin-x86_64, macos-15, x86_64-apple-darwin) (push) Successful in 3m53srelease / publish (push) Successful in 15sci / check (push) Successful in 10m18sci / coverage (push) Successful in 1m53sci / live-e2e (push) Successful in 1m53sreleased this
2026-06-29 22:45:47 +00:00 | 51 commits to main since this releaseDownloads
-
Source code (ZIP)
2 downloads
-
Source code (TAR.GZ)
2 downloads
-
Source code (ZIP)
-
v0.2.0
StableAll checks were successfulrelease / build (catthehacker/ubuntu:act-22.04, linux-x86_64, docker, x86_64-unknown-linux-gnu) (push) Successful in 1m28sci / check (push) Successful in 1m50sci / coverage (push) Successful in 2m0srelease / build (darwin-aarch64, macos-15, aarch64-apple-darwin) (push) Successful in 3m49srelease / build (darwin-x86_64, macos-15, x86_64-apple-darwin) (push) Successful in 3m40srelease / publish (push) Successful in 16sreleased this
2026-06-02 22:58:18 +00:00 | 128 commits to main since this releaseDownloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
1 download
-
Source code (ZIP)
-
v0.1.3
StableSome checks failedrelease / build (rust:1.95-bookworm, linux-x86_64, docker, x86_64-unknown-linux-gnu) (push) Failing after 1m12srelease / build (darwin-aarch64, macos, aarch64-apple-darwin) (push) Has been cancelledrelease / build (darwin-x86_64, macos, x86_64-apple-darwin) (push) Has been cancelledrelease / publish (push) Has been cancelledreleased this
2026-05-14 21:31:13 +00:00 | 187 commits to main since this releaseWhat's new
- Linux x86_64 binary ships in this release.
fj-v0.1.3-linux-x86_64.tar.gzis a statically-linked (against glibc ≥ 3.2) ELF that runs on Debian, Ubuntu, Arch, Fedora, NixOS, and most other modern distros. - Notarization scripts land for the next release.
scripts/notarize.shsubmits a signed darwin tarball to Apple's notary service;scripts/sign.sh --notarizechains signing → notarization in one step. The v0.1.3 macOS binaries are signed but not yet notarized — same signing identity as v0.1.2, so no new keychain prompt. - Windows claim removed from FAQ. Replaced with an honest "nobody's tried" + link to file an issue. The code still has
#[cfg(windows)]stubs but ships no binary.
Install
# macOS via Homebrew: brew tap rasterandstate/tap brew upgrade fj # Linux: curl -fsSL https://rasterhub.com/rasterstate/fj/releases/download/v0.1.3/fj-v0.1.3-linux-x86_64.tar.gz | tar -xz sudo mv fj-v0.1.3-linux-x86_64/fj /usr/local/bin/fj fj --versionOr download a tarball below. Verify with
shasum -a 256 -c SHA256SUMS.Build it yourself
cargo install --git https://rasterhub.com/rasterstate/fj --tag v0.1.3Full changelog: CHANGELOG.md.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
2 downloads
- Linux x86_64 binary ships in this release.
-
v0.1.2
StableSome checks failedrelease / build (rust:1.95-bookworm, linux-x86_64, docker, x86_64-unknown-linux-gnu) (push) Failing after 12m6srelease / build (darwin-aarch64, macos, aarch64-apple-darwin) (push) Has been cancelledrelease / build (darwin-x86_64, macos, x86_64-apple-darwin) (push) Has been cancelledrelease / publish (push) Has been cancelledreleased this
2026-05-14 21:31:13 +00:00 | 188 commits to main since this releaseWhat's new
Signed macOS binaries. Both
darwin-aarch64anddarwin-x86_64tarballs ship binaries signed with the Raster & State Developer ID (62Y3FRM8PD, hardened runtime enabled).The visible win: macOS keychain ACLs are scoped to code identity, not binary path. Now that releases share a stable signing identity, you click "Always Allow" on the keychain prompt once and don't get re-prompted on every fj upgrade.
Notarization is a follow-up. Signing alone solves the prompt-storm; notarization is what makes Gatekeeper not warn on first run for binaries downloaded fresh.
Also
scripts/sign.shships in-tree so contributors who build from source can sign their local binary and stop getting prompted. ReadsFJ_APPLE_DEVELOPER_IDfrom.env/.env.local.scripts/preflight.shaudits.env*files for secrets and grep-scans the working tree for PEM markers / Apple-password format / common token shapes. Wired into the pre-push hook..env.exampledocuments the release/signing variable names. Real values stay out of the repo.support@rasterstate.comis now the canonical contact email; personal-email references removed from tracked files.
Install
brew tap rasterandstate/tap brew upgrade fjOr download a tarball below. Verify with
shasum -a 256 -c SHA256SUMS.Full changelog: CHANGELOG.md.
Downloads
-
Source code (ZIP)
2 downloads
-
Source code (TAR.GZ)
1 download
-
v0.1.1
StableSome checks failedci / check (push) Has been cancelledrelease / build (rust:1.95-bookworm, linux-x86_64, docker, x86_64-unknown-linux-gnu) (push) Failing after 3srelease / build (darwin-aarch64, macos, aarch64-apple-darwin) (push) Has been cancelledrelease / build (darwin-x86_64, macos, x86_64-apple-darwin) (push) Has been cancelledrelease / publish (push) Has been cancelledreleased this
2026-05-14 16:36:39 +00:00 | 191 commits to main since this releaseWhat's fixed
-
fj auth setup-gitinstalled a broken credential helper. Git invokes the helper viash -c '<helper> "$@"' sh <verb>, sosed 's/^/password=/'consumed the trailingget/store/eraseverb as a filename. Everygit push/git pullagainst the configured host failed withsed: get: No such file or directory. Resolved inb68fb98, tracked in #1.The new helper wraps the pipeline in a shell function:
!f() { fj auth token --host <h> | sed 's/^/password=/'; }; fThree regression tests cover the structural shape, the verb-positional simulation, and a real
sh -cinvocation with a stubfjonPATH.
Recovery
If you installed v0.1.0 via Homebrew and ran
fj auth setup-git, re-run after upgrading:brew upgrade fj fj auth setup-git --host <hostname>This overwrites the broken
credential.https://<host>.helpergit config value with the new function-wrapped form.Install
brew tap rasterandstate/tap brew install fjOr download a tarball below and put
fjon yourPATH. Verify withshasum -a 256 -c SHA256SUMS.Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
2 downloads
-
-
v0.1.0
StableSome checks failedci / check (push) Waiting to runrelease / build (rust:1.95-bookworm, linux-x86_64, docker, x86_64-unknown-linux-gnu) (push) Failing after 1m13srelease / build (darwin-aarch64, macos, aarch64-apple-darwin) (push) Has been cancelledrelease / build (darwin-x86_64, macos, x86_64-apple-darwin) (push) Has been cancelledrelease / publish (push) Has been cancelledreleased this
2026-05-13 22:16:54 +00:00 | 207 commits to main since this releaseFirst tagged release of fj, a CLI for Forgejo in the spirit of
gh.See CHANGELOG.md for the full list of features.
Install
brew tap rasterandstate/tap brew install fjOr download a tarball below and put
fjon your PATH.Verify
shasum -a 256 -c SHA256SUMSDownloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
1 download
-
Source code (ZIP)