Changelog
OxygenHub runs at https://oxygenhub.io; each deploy's build is on the About page and at /api/v1/version.
Unreleased
- Diffusion (#148, docs/diffusion.md). When a repository that others pin as a subrepo moves on, each consumer that follows it gets a proposal moving the pin, signed by the hub, tested by the consumer's own pipeline, with the upstream's changes listed. One proposal per consumer and subrepo, kept up to date; never landed by itself. Turned on per repository (Settings › Diffusion,
PUT /repos/{owner}/{name}/diffusion, orfollowin.oxymodules), off by default;greenwaits for the upstream's run to pass. A repository's page shows who uses it and how far behind each is (Diffusion › Diffuses to,GET /repos/{owner}/{name}/consumers). Consumers are bumped on the bookmark their work lands on, learned from where their proposals land. - Diffusion in the API (#153):
GET /repos/{owner}/{name}/diffusiongives what a repository takes from and what it diffuses to, each with its pill, for the VS Code extension. - The card layout everywhere (#159–#164): the About card's counts in its heading and its buttons centred; wider repository cards with each Diffusion row on one line; the Code button and + New project in the accent blue; proposal and issue sidebars, profile and organisation pages, search results and the wiki's sidebar as cards; every panel's and form card's heading a shaded bar.
- Your sky (#157, docs/backgrounds.md): a quiet, moving backdrop behind the dashboard, drawn in Rust compiled to WebAssembly: your repositories as clusters and their last 30 days (every change pushed, proposal and issue events, releases, Flow runs) as marks around them; the cursor lights and links them, and names the cluster it rests by. Settings › Appearance: Moving, Still or Off; still under reduced motion.
- Signed-in pages are private (#158): HTML for a signed-in viewer says
Cache-Control: private, no-store, so no proxy or CDN serves one viewer's page to another. - The dashboard in cards (#155): each section a rounded card with its heading and count in a shaded bar, your repositories a sidebar card with New in its heading.
- Tidier pages (#152). The repository sidebar is a column of cards with shaded headings: Diffusion in one card (Takes from and Diffuses to, each with its pill), the ten most recently moved bookmarks with a link to the rest, and the hub's delegate key shown with the OxygenHub mark. The code tree has a shaded head with the latest change and the history, and folder and file icons; the site bar has icons; the Diffusion tables share one column grid; Help › Coming from GitHub covers submodules and Renovate.
- Admin › Runners › Images (#146): every runner image, where it is and what uses it.
- Faster pages after a restart (#145): bookmark summaries are kept on disk; slow requests are logged.
0.4.2 (2026-10-11)
-
Coverage from a workstation (#136).
oh coverage upload FILE [--bookmark B | --change HASH]keeps an LCOV or Cobertura report measured off the hub as the coverage of that state, for suites a runner cannot run in full. The Coverage page, its history and the Reports card say "uploaded by NAME" where a run number would be, so a runner's figure and an upload's are never mistaken for each other, and an upload sets no check. -
Images by hash (oxygenflow#39). Rootfs images live on the hub:
flow rootfs pushsends only the chunks the hub lacks (a rebuilt image costs its changes, runs of zeros cost nothing), the hub checks the whole image against its hash, and a runner that lacks a job's image fetches it instead of anyone copying it between machines. The hub offers such a job to runners that fetch, and says when neither the runner nor the hub holds the image. -
Reports (#127). A Flow job's
report "NAME" { path … }output is kept file by file with every run, and a repository with reports gets one Reports tab in place of Coverage and Tests: each report's latest run with its kind, summary and metrics, and how each moved against the run before. A report's page shows its metrics with sparklines across runs, its thresholds, its[context], its runs, and its own page (index.html) in a sandboxed frame that carries no session. Each metric withfail_aboveorfail_belowsets a check namedreport/NAME/METRICon the change. -
Flow's artifacts survive the nightly gc (#126). They are named only in the hub's database, so garbage collection found them unreachable and would have deleted each one 14 days after its run: coverage files, test reports and dist files alike. The nightly gc now keeps every artifact a repository's runs name.
-
Transfer a project (#125). A board's Settings move it to another owner you may make boards under, such as one of your organisations; so do the API (
owneronPATCH /projects/{owner}/{n}) andoh project transfer N --to OWNER. It takes the next number there, with its columns, cards and privacy, and its old address redirects to it.
0.4.1 (2026-10-10)
-
The store as its provider's icon (#123). A repository kept in its owner's bucket shows the icon of Google Cloud Storage, Amazon S3 or Azure Blob Storage in its header; hovered, focused or tapped, a card gives the provider and, for its admins, the bucket, prefix, region and URL, with a link to Settings › Storage.
-
A tidier Projects page (#124). A filter and the New project button in one bar, Open and Closed tabs with their counts, a row per project with its board icon, privacy, number, last update and description; the new-project form opens above the list.
-
Making projects, and their references (#118, #119). The Projects page has a New project button for anyone who may make one, links to the organisations' boards from your own, and a Projects tab on every repository; a card or issue reference may name another repository (
owner/repo#N).oh projectlists, creates and edits boards (#115). -
Blame of a conflicted file no longer fails (#120). Its conflict markers are lines no change wrote; the page gave an error, it now shows them with an empty blame cell.
-
Test reports (#114). A Flow job's JUnit XML artifact named
junitis read like a coverage report: a Tests tab with each run's passes, failures and skips, every case's history, and the flaky ones. A failing job's artifacts kept withwhen "always"are shown too. -
Pushes in parts. A long push from oxy 0.4.3 arrives as several requests, each of objects alone but the last; the hub stores each part and records the push once, when the last names the bookmark.
-
Memory under control (#116, #117). The cache of loaded states keeps to a size budget (
OXYHUB_STATE_CACHE_MB, 3 GiB) as well as a count, the summary of a bookmark's heads is cached once per state, and the allocator returns freed memory;/healthreports the cache. -
A coverage check (#113). Each coverage report sets a
coveragecheck, such as81.9% (-0.3 against main), measured against the default bookmark. Settings › Coverage can set a minimum and a largest drop. The check then fails and says why, and as a required check it holds a proposal back. A proposal's report shows the files whose coverage moved against the default bookmark. -
Coverage reports, built in (#113). A Flow job's artifact named
coveragein LCOV format is read when uploaded, and the job's log says what it found. A Coverage tab shows the latest percentage and its change over a chart of every run, a strip of facts (lines covered, hits per line, functions, branches, files, badge with its Markdown), what this run changed and the files whose coverage moved, how files spread over coverage bands, a chart of covered and missed lines, the recent runs, every file with a bar, and each file's lines marked run or missed, as Coveralls does. A badge is at/OWNER/NAME/coverage/badge.svg, and the run page shows each job's coverage. Totals matchcargo llvm-cov's own. -
A wiki can start with a push. Someone who may write to a repository can push the first pages of its wiki to
OWNER/NAME.wiki, as well as writing them on the web: the hub makes the empty wiki when a writer first reaches that address, and the push gives it its id. Readers still find nothing there until it has pages. -
Mermaid diagrams in Markdown. A
```mermaidblock in a README, wiki page, issue or proposal is drawn as a diagram, in the page's light or dark theme. The hub serves its own copy of Mermaid 12.1.0, loads it only on pages that have a diagram, and runs it in strict mode. A diagram that does not parse stays as its source, with the reason under it. -
An organisation's storage policy (#96). An organisation's Settings › Storage can require every new repository to live in a bucket and list the buckets allowed, so its data stays where its own buckets are. The New repository form refuses anything else and says what is allowed.
-
A repository can live in its owner's bucket (#96). The New repository form has a Storage section: this server's disk, or your own Amazon S3, Google Cloud Storage or Azure bucket, with credentials for it. The bucket is checked before anything is made. The credentials are sealed on the server and used only for that repository. The hub keeps a cache in front. The header shows a bucket tag, and Settings › Storage checks the bucket again and replaces the credentials. Tested with a push into a real S3 bucket.
-
Runners decline, and the queue says why a job waits (oxygenflow#36, OxygenFlow spec §11.4–11.5). A runner can hand back a job it cannot run with a reason. The job is queued again and not offered to that runner while what it offers stays the same, for ten minutes at most. After three different runners decline it, the job ends as unplaceable with their reasons. Every 15 seconds the hub works out why each ready job no runner can take is waiting, from each runner's last claim: no runner online, a missing tier, the wrong architecture, an image it does not hold, a label, memory or CPUs, a scope, or a decline. The run page and the API show the reason, and a runner's page shows its tiers and what it declined.
-
Assistants through the API and
oh(§13.4). List, add or change, and remove a repository's, an organisation's or the site's assistants, and read or set their prompts:/repos/{o}/{n}/assistants,/orgs/{org}/assistants,/admin/assistants;oh assistant list|add|remove|promptwith--orgor--site. Tokens are write-only and read from an environment variable byoh. -
Adding an assistant picks from what the provider offers (#109). A provider list fills the name, endpoint and API (with Other for anything else), List models fetches the endpoint's own models into the Model field, and a model the endpoint does not list is refused on save with ones it does. Administration is a sectioned page with a left-hand menu, its audit log paged (#110).
-
Agents get their marks, contributors one row each. A repository's contributors show one row per account with each agent's share beneath (
↳ via Claude Code · 1), asoxy logdoes; agents' names on change pages, reviews and the ask buttons carry their brand mark (Claude, ChatGPT, Perplexity, DeepSeek, Mistral; Antigravity with the Gemini mark), served at/avatar/agent/{name}. -
Jobs go to runners of the right architecture (oxygenflow#36). Runners report each image's architecture; the hub matches a job's
archor its image's against the runner's, and the queue says what a waiting job asks for. -
Sign in with GitHub or Google (#106). Set up per provider under Administration (client id, sealed secret, on or off); the login page offers them. A first sign-in links the account with the same verified email, or makes an account when registration is open; connect or disconnect under Settings › Security.
-
Wikis (#104). Each repository can have a wiki: Markdown pages kept as an Oxygen repository of their own, read and edited on the web (with history, a sidebar, a page list and search) or cloned and pushed as
OWNER/NAME.wiki. Web edits are signed changes, as code edits are. Settings › Advanced deletes the wiki data. -
Advanced settings: transfer, archive, delete (#108). A repository's Delete section becomes Advanced: transfer the repository to a user or organisation (the old address redirects and
oxyremotes keep working), archive it read-only with a reason and bring it back, delete it, and a placeholder for deleting wiki data ahead of wikis. -
Accounts elsewhere on a profile (#105). Bluesky, Mastodon and GitHub under Settings › Profile, checked and normalised (a handle,
@user@instanceor a profile URL all work), shown on the profile with the service's icon asrel="me"links. -
A job's identity: the hub is an OIDC issuer (#103). A job that declares
identity "AUDIENCE" { mount-path … }gets a signed token naming the repository, bookmark, run and job, delivered as a file like a secret;/.well-known/openid-configurationand/.well-known/jwks.jsonpublish what AWS, Google Cloud or Vault need to trust it. Tokens are masked in logs. -
Secrets for pipelines (#107). Administration, organisation and repository settings hold secrets, typed once and sealed; a job gets only those its pipeline declares with
secret, as files Flow mounts before it starts, never in the environment; their values are masked in the job's log as it arrives. -
Variables for pipelines (#102). Administration, organisation and repository settings hold variables for OxygenFlow jobs, inner levels overriding outer, each page showing what it inherits; a claim carries the repository's effective set and Flow adds them to the job's environment under
FLOW_*and the pipeline's own. Not for secrets, which Flow mounts. -
AI assistants, step three: drafted descriptions (#97). The new-proposal form and an open proposal's editor have a draft-with button per assistant; the answer, written from the changes and diff, lands in the editor for the person to change before saving.
-
AI assistants, step two: review requests (#97). Under Reviewers, the author or a writer asks an assistant the repository may use; the hub sends it the proposal's title, description, changes and diff with the site, organisation and repository prompts, and records the answer as a comment review via the assistant, shown as "bob ↳ agent Claude". Approvals count only people, and an assistant's answer never replaces the asker's own verdict. Also
POST …/proposals/{n}/review-requestsin the API, withviaon reviews. -
AI assistants, step one: settings (#97). Administration, an organisation's settings and a repository's settings each hold a list of assistants (name, endpoint, API shape, model, extra headers, token, context budget, on or off) and a prompt. A repository sees its organisation's and the site's beside its own; the innermost of a name wins and one turned off above is off below. Tokens are sealed with ChaCha20-Poly1305 under
keys/assistants.keyand never shown again; TEST sends a one-line exchange. Review requests and drafted descriptions follow. -
A runner's own labels and time limit (#98). A runner's page under Admin → Runners has a Settings form: labels an administrator gives it beside those it reports (jobs that ask for a label match either), and a job time limit in minutes below the site's, applied when it claims.
-
Mail settings on the admin page (#101). The SMTP URL and From address are set and saved from Administration, overriding
hub.toml's[email], with a button that sends a test message to the administrator's own address; the password is kept on the server and shown masked. An empty URL turns mail off. -
Spacing (#99, #100). The Add a runner button stands off the runners table and the webhook and collaborator forms fill their panel; a proposal's project entries keep a space before remove.
0.4.0 (2026-10-04)
-
Who did the work. A change an AI agent signed for its author says so wherever the author is shown:
via NAMEwith anagentbadge in history rows, aviarow under the author on the change page (the agent's identity, the delegation it acted under and its expiry, its key), a count under the author in Contributors, andviain the API's change summaries. The author stays the human; a revoked delegation turns the badge red. -
Rotated identities link themselves. A push that brings changes authored by a successor of a linked identity, where every step kept the key pair (a renewal, a delegation added or removed), links the successor to the same account: delegating to an agent no longer leaves your changes unattributed until you re-upload a key.
-
A Flow badge.
/{owner}/{name}/flow/badge.svgis the latest run on the default bookmark as a small SVG for a README (passing,failing,running,no runs). -
History by revset.
GET /repos/{o}/{n}/history/{ref}?r=REVSETanswers the changes a revset selects (Oxygen'sdocs/revset_query_language.md), under a two-second budget. -
The hub garbage-collects every repository nightly (#94).
oxy gcruns for each repository in turn atretention.gc_hourUTC (default 3; 24 or more turns it off), under its write lock, logging what was pruned and packed;oxygenhub gcruns the same pass by hand. The oxygen repository had 1.45 GB of loose objects when this was found. -
Milestones over the API, and
oh milestone(#91).POST /milestonescreates one (title, description, due date) or changes the one with that title,PATCH /milestones/{title}changes title, description, due date or state;oh milestone list|create|edit|close|reopen. -
oh issue edit(#95). Title, body (-Ffor a file), labels and assignees as whole sets (--no-labels,--no-assignees), milestone (""clears);oh issue labelkeeps adding and removing. -
ohdies quietly on a closed pipe (#92), asoxydoes:oh issue list | head -1no longer panics. -
The trust report prints the tagged state once (#93): "The tagged state has N heads" with the short heads, and each asset's line names the run that built it.
-
A runner busy with its only job stays online (#78). Its log batches and finish call mark it seen, where only claims did.
-
The hub signs for you only with
changes,refs. Delegating to the hub key now takes--scope changes,refs; the recipes and the trust page say so. -
Web workspaces, designed (#69). Spec §13.3 has the design to review before anything is built: data, the runner's
workspacejob kind with flow-casfs, the owner-only proxy, delegate-signed pushes tows/{account}/{id}, security, costs, open questions. -
What starts a job:
on(oxygenflow#24). A pipeline's or a job'son { push "main" "feature/*"; tag "v*"; manual }decides which events run it: pushing a tag now runs the jobs that name it, on the tagged state; a run has only the jobs its event starts (and what they need), and an event that starts none starts no run. A rerun repeats its run's event. A bookmark sent again unchanged starts no run.schedule "0 3 * * *"lines run their jobs on the default bookmark when due (UTC), checked each minute.
0.3.1 (2026-10-02)
-
ohfor RISC-V and from apt.oh0.3.1 comes for 64-bit RISC-V Linux too, its .deb adds the apt repository on its first install, and every file is built by OxygenFlow (attested on the release's trust page). -
Runner settings for the site, and a job's own time limit (#89, oxygenflow#23). Admin → Runners → Settings sets the default job time limit, the most a pipeline may ask for, and how long past its limit a silent job is given up; saved in the database, over hub.toml's
job_timeout_minutes. A job runs under its pipeline'stimeout(on the job or its sandbox), capped at the maximum, else the default; the limit is recorded on the job when it is claimed, and the lost-job reaper uses each job's own. -
A job's
scopeis honoured (#88). A run a push starts leaves out each job whosescopeglobs none of the files the push's new changes touch (since the bookmark's previous run), unless a job that runs depends on it; a bookmark's first run, "Run pipeline" and reruns keep every job. A push that touches no job's business starts no run. -
Modern form controls everywhere (#87). Every text field, textarea and dropdown on the site is rounded and padded, rings in the accent colour on focus, and dropdowns draw their own chevron; checkboxes and radios take the accent colour. On issues and proposals, Projects is a gear menu like Labels and Reviewers instead of a dropdown and Add button.
-
A tidier proposal conversation (#86). A pencil by the title and a ··· menu on the description (Copy link, Copy Markdown, Edit) edit the title, target and description in place, replacing the box at the foot of the page. The comment box has one action row (Back to draft, Close proposal, Comment); the review is a compact card with Comment / Approve / Request changes as one segmented control beside Submit. A landed proposal ends with a "Proposal landed" card (who, how many changes, onto which bookmark), offering to delete the source bookmark while it is still there; a closed one with a "Proposal closed" card.
-
An apt repository at
/apt(#85). The hub serves the.debassets of the releases of the repositories named in[apt] repos(public ones only) as a signed apt repository:dists/stable/InRelease,ReleaseandRelease.gpg,main/binary-{amd64,arm64,riscv64}/Packages(.gz), and the pool. The index is rebuilt when a release's packages change, and signed with the key in the data directory'sapt-gnupg(public key at/apt/oxygenhub.gpg)./aptsays how to add it. -
The proposal page's sidebar matches the issue page's (#84). Sections with headings; Reviewers and Labels open filterable menus from a gear (people with access to the repository, with avatars; labels with colour and description) that apply in one go; requested reviewers show with avatars; Landing, Checks, Approvals, Projects, Code owners and Depends on as sections. The reviewers form takes the whole list (
action=set). -
Device sign-in says it is done (#83). After Approve,
/deviceshows "Signed in: oxy on saturn is now signed in as you; return to it, there is nothing to copy or enter" with a link to its token, instead of the code form again, which read as if the code were needed a second time. Denying shows a matching "Denied" card. -
The issue page's sidebar, laid out as GitHub's (#82). Assignees, Labels and Milestone each show what is set under their heading (avatars, coloured label pills) with a gear opening a menu: a filterable checklist (people who can be assigned, with avatars; labels with their colour and description; a milestone) that applies in one go. "Assign yourself" when nobody is assigned. Close, Not planned, Lock and Pin are quiet rows with icons at the foot, evenly spaced, and the comment box is as wide as the conversation.
-
Disable and delete accounts (#81). Administration offers Disable and Enable beside Ban: a disabled account is refused like a banned one (sign-in, sessions, tokens, pushes), and signing in says "Your account has been disabled. Contact the hub's administrator." Delete removes an account for good after its username is typed again: its tokens, keys and memberships go, what it wrote stays under its name, and the name stays retired so nobody can take it over. It is refused, naming them, while the account owns repositories or is the only owner of an organisation; nobody disables or deletes themselves or the last admin who can sign in. A deleted account's proposal reviews now stay, as its comments do.
-
Labels from
oh(#80).oh proposal label N --add NAME --remove NAMEandoh issue label N …put labels on or take them off (triage), andoh label list|create|deletemanage the repository's labels. Through the API,PATCH /proposals/{n}takeslabels(the whole set, by name, every name checked before anything changes), and a proposal's JSON lists its labels. -
A clearer release trust page (#79). A row of badges at the top says whether the tag is signed and by whom, how many changes are signed or sealed, and how many assets an attested OxygenFlow build produced. The graph is drawn at its own size (it filled the screen), drops the builds column when nothing was built, and marks every box: a lock where the claim is signed and checked,
!where it is not (unsigned, uploaded by hand),✘where it fails, with the reason underneath and the details on hover. Each asset's heading carries its badge too. The GitHub look no longer strokes the graph's text.
0.3.0 (2026-10-02)
-
Edit a proposal and comment on one from
oh(#76).PATCH /api/v1/repos/{owner}/{name}/proposals/{n}changes the title or description, for the author or a maintainer as on the page;oh proposal edit N [-t] [-b | -F FILE],oh proposal comment N -b/-F, andoh proposal create -F FILE("-" reads stdin). -
A restarted runner's lost jobs end at once (#71). A runner lists the jobs it holds at each claim (OxygenFlow does), and the hub ends any other job it has as running on that runner, with the reason, rather than an hour later at the time limit.
-
Edit a release (#72).
PATCH /api/v1/repos/{owner}/{name}/releases/{tag}changes a release's title, notes or prerelease flag, keeping what it is not given, andoh release edit TAG [--title] [--notes TEXT|-] [--prerelease true|false]sends it.oh api -X METHODwithout a body now sends the method asked for (#75). -
Runners get only jobs they can run (#73). A runner that lists the rootfs images it holds (OxygenFlow does, at every claim) is offered only jobs whose image is among them, rather than jobs it would fail for want of one.
-
Lost jobs end by themselves (#71). A job still running five minutes past the job time limit is one its runner no longer reports (it restarted, or lost the hub as the job ended). The hub now looks for those once a minute and ends them as errors with the reason, skipping the jobs that depended on them and settling the run, so a runner is not shown busy with jobs it is not running. Earth's reset stranded five such jobs, closed by hand.
-
A GitHub-like look, now the default (#74). Primer colours in light and dark, system fonts, rounded boxes, underline navigation, grey and green buttons, sentence case and pill labels, for readability and familiarity, light by default (the theme toggle and Settings → Appearance still offer dark and matching the system). The RetroPlay look stays available:
?look=classicon any page keeps it for that browser, and?look=githubreturns to the default.
0.2.0 (2026-10-01)
-
One sign-in for oh and oxy (oxygen#47).
oh auth loginkeeps its token in oxy's credential store (~/.config/oxy/credentials.toml, or the keyring throughauth.helper), andohfinds tokens there, sooxy auth loginsignsohin too and the reverse.~/.config/oh/config.tomlnow holds only the default hub. Pinned oxygen brings remote bookmarks by name, and clones check out the repository's default bookmark. -
Device tokens expire, and the approval page says where a request came from (#70). A token from device sign-in lasts 90 days past its last use; Settings → Tokens shows its expiry, renewed on use. The
/devicepage shows when the code was asked for and from which address (the client's, through the proxy whenbehind_proxyis set), and warns to approve only a code you started yourself.DELETE /api/v1/user/tokenrevokes the token it is called with, whichoxy auth logoutuses. -
Device sign-in (#67). An app gets a token without a pasted secret. It asks
/api/v1/device/codefor a code and shows it; you approve the code at/device, signed in with your second factor, choosing what the app may do; its next poll gets the token.oh auth loginsigns in this way;--tokenstill takes a token. -
Review requests (#68). The author or a writer asks someone to review under Reviewers on the proposal page, or with
POST .../proposals/{number}/reviewers. The person asked is notified, and their review answers the request. Every review now tells the author. -
Runner scopes (#41, part 2). A runner serves the whole site, one owner's repositories, or one repository, set when its registration token is made or later on Admin → Runners. It never takes another scope's jobs.
-
Flow says which subrepos a job lacks (#56). A job's log starts with each pinned subrepo it was not given (the run's actor cannot read it, or it is not on this hub) instead of failing later on missing files.
0.1.0 (2026-09-30)
ohfor Linux, macOS and Windows. The first downloadable builds ofoh, OxygenHub from the command line, beside Oxygen's own 0.1.0. A prerelease.- The hub moved to oxygenhub.io, serving on localhost behind the tunnel.
- Proposals take
feature/…bookmarks, record who landed them and how, and say how to resolve a conflict. Organisation repositories with two or more members require one sealed approval by default. - A Code button with the clone command and a zip download.
- Release assets up to the configured limit through the API.