Skip to content

Nen v0.19.0

Choose a tag to compare

@zheref zheref released this 04 Oct 18:21
· 363 commits to main since this release
384711e

v0.19.0 — 2026-10-04

Release unit for v0.18.3..v0.19.0. It brings interactive desktop runners, the runner trust fixes, and stricter readiness:

The compatibility floor moves to 0.19.

Added

  • runner (#335, closes #333) — interactive Windows runners, which run inside a signed-in desktop session for jobs that drive a real UI. A service runs in session 0, where such a job times out.

    • Declaration: nen/workflow.json → runners.pools[].mode is service or interactive.
      • Absent means service on Windows and Linux, and interactive on macOS, whose runner is a LaunchAgent.
      • macOS + service and Linux + interactive are refused.
      • An interactive Windows pool's labels are exactly ["self-hosted", "Windows", <arch>, "desktop"].
    • runner plan: its contract is now nen.runner.plan/v0.2, which adds mode and dailyAccount; a v0.1 plan still reads, as a service plan.
      • An interactive identity must be a local account; the built-in service identities are refused.
      • Every rendering says the runner is online only while that account is signed in, with an active, unlocked session.
      • It also says such a runner takes every job aimed at the plain [self-hosted, Windows, <arch>] set, not only desktop jobs.
      • When the identity is the Windows account computing the plan, the plan warns and records dailyAccount: true.
    • runner script: a dailyAccount plan renders only with the new --accept-daily-account, and the verb repeats that check itself on Windows.
      • For an interactive pool the script installs no service and asks no password.
      • It resets each Runner<N> folder to the locked root's baseline, then grants the identity Modify there.
      • It registers a Scheduled Task at that account's logon that runs run.cmd in its desktop session: interactive token, priority 4, no time limit, restarted on failure.
      • A folder still installed as a service is refused.
      • The service-form script is unchanged.
    • runner workflow: it fills an eighth value, @@MODE@@. A template that predates it still renders. nen's fixture template gains a desktop-session probe, which fails in session 0, and mode-aware remediation.
  • runner (#336, closes #330) — on the pool's own host, runner plan reads the existing actions.runner.* services read-only (Get-CimInstance Win32_Service / systemctl).

    • It warns on stderr, at exit 0, when the planned account already serves a repository of different or unknown visibility, and recommends a per-repository account (runner-<repo>).
    • It also names the target's own runners left on such an account.
    • Nothing is refused, and nen never creates an account.
  • pr (#355, closes #345) — nen pr mark-ready --target <owner/name> --pr <n> [--require-head <sha>] [--dry-run] is the explicit draft-to-ready transition, now that pr ready fails a draft at row 1. Its contract is nen.pr.mark-ready/v0.1.

    • It reads the PR first. Exit 2 means a target that does not resolve, exit 3 a closed or merged PR, and exit 8 a head-pin mismatch. A PR already out of draft is already-ready at exit 0, with nothing sent.
    • It sends markPullRequestReadyForReview by node id, on gh's own credential.
    • It reports marked-ready only when a read-back shows isDraft:false. A head that moved during the call is marked-ready-head-moved at exit 8.
    • Every other pr flag is refused at exit 2, with no gh call.
  • commit (#357, closes #273) — nen commit write and nen wc squash read the commit they just wrote back through git's own trailer parser (git cat-file commit piped through git interpret-trailers --parse --unfold).

    • A new injected member lists every refused trailer the written commit carries: a key a hook added that the policy refuses or that ends in -by/-with without being admitted, or a refused key the message itself carried.
    • Either case is exit 3. The commit is left in place, never amended, and stderr names the undo.
    • On a real write, commit write --json's trailers is read back from the commit.
    • USAGE § Exit codes gains one table of every verb that returns a code above 2.

Fixed

  • runner (#334, closes #319) — runner enable certifies only a workflow_dispatch run on the default branch that ran the default branch's own preflight blob.
    • runner script treats a configured Runner<N> as stale when its service is absent or GitHub no longer lists it. It re-registers that runner with --replace instead of counting it as skipped.
    • For an interactive runner, which has no service, only GitHub's listing makes it stale (#335).
  • gates (#342, closes #317 and #331) — pr ready no longer counts a head as green when all of its checks were skipped; it needs at least one success.
    • A draft PR is not-ready at row 1.
    • A queued re-run is the latest run of its check, and holds row 2 until it concludes.
    • pr next-blocker --json's kind gains draft.
  • surface (#343, closes #328) — a mirror's summary: line now parses as YAML for every description, block scalars included.

Breaking / consumer notes

  • Repin: "0.18" → "0.19", and v0.18.3 → v0.19.0. The maintainer moved the floor for this release because changes in #334, #335, #342 and #357 alter meaning on unchanged inputs. A repository whose dependency.minimum is below 0.19 is refused by this build at exit 5, and must raise its minimum and repin. zheref/hatsu declares 0.18 (bootstrap ref v0.18.2); its repin rides with zheref/hatsu#206, which adopts mode and @@MODE@@.
  • gates — pr ready answers not-ready where it answered ready (#342): for a head whose checks are all skipped, for a draft, and for a check whose latest run is a queued re-run. A repository whose only checks are conditional must make one job run and succeed on every head.
  • runner — runner enable exits 1 for a run it accepted before (#334): a push run, or a run on a branch other than the default. Dispatch with nen runner preflight on the default branch and pass that run id.
  • runner — runner plan --json / --out names the contract nen.runner.plan/v0.2, where it named v0.1, and carries mode and dailyAccount (#335). A reader that matches the contract string must accept v0.2. runner script still reads a v0.1 plan.
  • commit — commit write and wc squash exit 3 when the written commit carries a refused trailer (#357). Exit 3 means the commit exists, so a caller that reads any non-zero exit as "nothing was committed" must handle it.
  • wc — wc squash loads nen/workflow.json on every run, before anything moves (#357). A malformed file is exit 1 even with --base and a message with no trailers, and a broken policy with a bad message is exit 1, not 2.
  • surface — on repin, Hatsu's surfaces/codex/ten/SKILL.md reads stale until one nen surface mirror generate for codex: its summary: gains double quotes (#343).