Skip to content

v7.198.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 21:35
7f8ef64

Fragment gate: path-list transport, listing integrity, and job bounds

What

The pr-standards fragment-gate arms now transport their changed/added path
lists to the classifier in a newline-safe framing, the dispatched arm validates
its files listing, and the job is bounded.

Why

Both arms moved path lists as line-delimited text. core.quotePath=false only
stops git from C-quoting non-ASCII bytes: a path containing a quote, backslash,
or control character still arrived C-quoted and matched no path class (the PR
was waved through as "not user-visible"), and a path containing a newline could
not survive the transport at all — while the quality gate's own git diff -z
view classified the same path as user-visible, so two required checks could
disagree about the same change. The dispatched arm also made two independent
files-API listings (a push between them produced split-brain lists), never
checked the listing against the PR's changed_files count (the API truncates
beyond 3000 files), and never re-asserted its head guard after listing. The
pr-standards job also lacked a timeout-minutes, its pull_request checkout
persisted the job token for PR-authored code to read, and the classifier's
stdin read was unbounded.

Change

scripts/check-pending-fragment.ts --print-requirement gains
--paths-format <newline|nul|jsonl> with --changed-file/--added-file
inputs (byte-bounded at 4 MiB; stdin is bounded twice — an input over the
bound exits 2, and one that produces no data within an idle bound exits 2
instead of pinning the check until the job ceiling; file inputs require an
explicit --paths-format so framed bytes can never silently decode through
the legacy newline framing). The pull_request arm feeds git diff -z temp files; the
dispatched arm materializes ONE gh api --paginate ... --jq '.[]' listing,
fails closed when the entry count differs from the PR's changed_files or the
head moved after the guard, and derives both lists as JSON-encoded paths. The
job declares timeout-minutes: 15 and both checkouts set
persist-credentials: false. The legacy newline stdin mode and --added
flags are unchanged (release-and-publish.yml depends on them), and the
recorded workflow hash in docs/ci/required-check-evidence.json is refreshed
in the same change.

Behavior change

A changed path that git C-quotes (or that contains a newline) now reaches the
same verdict in both pr-standards arms as in the quality gate. A truncated
files listing or a PR head that moves during the dispatched validation fails
closed with an explicit error instead of classifying a partial snapshot. An
input stream larger than 4 MiB makes the classifier exit 2 (unusable) instead
of reading unbounded input.

Migration

None. The classifier's legacy invocation shapes keep working; the new
--paths-format mode is additive.


Task-dispatch model failover works for underscore-named agents (issue #2960)

What

Fixed the Task-dispatch model-fallback chain resolver so an installer-seeded,
underscore-named agent (e.g. test_engineer, critic_oversight,
curator_phase, skill_improver, spec_writer) resolves its configured
fallback_models in the default swarm.

The resolver derived the swarm id with a naive first-underscore split, so
test_engineer produced swarm id test instead of the default swarm — the
fallback chain collapsed to empty, and ONE retryable provider failure (HTTP
429/503-class) advanced the route to exhausted. The next chat.message for
that child session then threw MODEL_FALLBACK_EXHAUSTED, killing the whole
in-flight dispatch on a single provider blip instead of failing over.

The swarm id is now derived by length delta from the canonical role (the same
pattern the curator/skill-improver/turbo failover factories already use), so:

  • All 14 installer-seeded underscore agent names resolve their seeded
    fallback_models chain in the default swarm; one retryable failure now
    advances to the seeded fallback model instead of exhausting the route.
  • The designed terminal state is unchanged: exhausting the full
    primary+fallbacks chain still throws MODEL_FALLBACK_EXHAUSTED.
  • Prefixed multi-swarm names (local_test_engineer) and unprefixed names
    (coder) resolve exactly as before, including names whose casing or
    separator differs from the roster (Coder, code-reviewer) — those keep
    resolving via the default swarm.
  • Documented behavior change (fail-closed direction): an UNPREFIXED canonical
    name whose first token collides with a real swarm id (e.g. test_engineer
    with a swarm named test) now resolves via the default swarm instead of
    accidentally hitting that swarm; use the prefixed name (test_test_engineer)
    to address it. A case-variant spelling (Test_Engineer) now also resolves.

Why

Workstream J slot J13 (frontier-audit finding t1-src-agents-2/S1, dual-review
UPHELD HIGH): fresh installs seed DEFAULT_AGENT_CONFIGS — 14 underscore
agent names with non-empty fallback_models — so the broken path needed no
manual configuration to be live.


Bubblewrap capability probe: fix temp cleanup and the false fs=weak verdict

Fixes two defects in the Linux bubblewrap capability probe in src/sandbox/capability-probe.ts, which feed /swarm diagnose output and the require_filesystem sandbox requirement check.

Changes

  • Temp cleanup no longer fails (and leaks): the probe removed its temp directory with fs.rmSync without recursive, which throws ERR_FS_EISDIR on Node and EFAULT on Bun for any directory. Every probe therefore left a swarm-bwrap-probe-* (or swarm-seatbelt-probe-* on macOS) directory in the OS temp dir and reported a misleading probe temp cleanup failed reason. Cleanup now passes { recursive: true, force: true }, and the default _internals.removeProbeTempRoot applies the same flags.
  • Correct filesystem verdict on working bubblewrap hosts: the probe now binds its temp root read-only (--ro-bind tempRoot tempRoot) before the writable allowedRoot bind. Previously bubblewrap created the unbound parent in its writable tmpfs root, so the out-of-scope probe write succeeded inside the sandbox and the probe reported filesystem: weak on hosts where isolation works. Such hosts now report filesystem: real, so require_filesystem checks that were wrongly failing on them pass.
  • Tests: the probe test now pins the --ro-bind argument and its order before --bind, the cleanup flags, and that the non-empty probe directory is really removed.

Declarative SDD provider selection (SWARM_SDD_SOURCE and sdd.source)

What changed

When a repository has both OpenSpec (openspec/) and Spec-Kit (.specify/) sources and no native .swarm/spec.md, readEffectiveSpecSync reports an ambiguity and returns no effective spec. Resolving it previously required the interactive --source flag of /swarm sdd, which gates, hooks and other non-interactive consumers cannot pass. The provider can now be selected declaratively:

  • sdd.source in the plugin config (.opencode/opencode-swarm.json, or the user config at ~/.config/opencode/opencode-swarm.json; the project file wins) accepts openspec, speckit or swarm. It is a registered config key, validated by the schema, covered by /swarm doctor, and listed in the generated config reference and the shipped JSON Schema.
  • SWARM_SDD_SOURCE (environment variable, case-insensitive) accepts the same values and takes precedence over sdd.source.
  • Precedence is: a native .swarm/spec.md (always wins) → --source → SWARM_SDD_SOURCE → sdd.source → auto-detection. With nothing set, behavior is unchanged.
  • /swarm sdd status, validate and project use the same resolution, so the CLI and the drift/preflight gates no longer disagree. status reports Source selection: … when the provider came from the environment or config.

Safety behavior

A selected provider is final, exactly like --source: if it yields no spec (for example SWARM_SDD_SOURCE=openspec in a repo that only has .specify/, or swarm without a .swarm/spec.md), the repo is not silently re-auto-detected. Instead a one-time advisory (visible in /swarm diagnose) names the selection and the repository, because a missing effective spec downgrades the drift gate to advisory and skips requirement-coverage preflight. An unrecognized SWARM_SDD_SOURCE value is likewise reported once and ignored rather than dropped silently. An invalid sdd.source in a config file is a validation error like any other invalid key: the loader reports it (and /swarm doctor names sdd.source), the loader's fail-secure recovery then applies exactly as it does for every other key (with a user-level config present the project config is discarded until the value is fixed), and the SDD resolver additionally buffers its own advisory and falls through to auto-detect. The resolver reads sdd.source directly from the config files (silently; file-level load problems stay with the full loader), so it can still honor sdd.source from any project config the loader discarded (another invalid key, or a refused prototype-pollution payload such as __proto__) — sdd.source is a three-value provider selector, not a security setting.

Migration

No migration required. Nothing changes unless SWARM_SDD_SOURCE or sdd.source is set. Because the environment variable is process-wide, prefer sdd.source in the project config for per-repository pinning and reserve the variable for CI or one-off overrides.


What changed

  • PR-review reviewer and critic lanes no longer void themselves on stray rows:
    a verdict row whose post-parse arity is illegal for its marker (a Stage-B
    task-keyed [REVIEWED] row, an echoed format line) is quarantined into
    diagnostics (identifier and arity only) instead of failing the exact-set
    contract and discarding every valid sibling row. A legal-arity row naming an
    unassigned ID still voids the lane (spoofing floor unchanged), an assigned
    item whose only row was quarantined stays unclaimed, and a quarantined row
    never becomes a claim.
  • Reviewer lanes are told at dispatch that the Stage-B "STRUCTURED VERDICT
    LINE" mandate does not apply to them (the system prompt is static, so the
    suppression is a controller-composed protocol clause plus a conditional
    sentence in the reviewer prompt; non-PR reviewers keep the Stage-B contract).
  • Operator prompts for reviewer/critic lanes are rejected side-effect-free when
    they embed [REVIEWED] | / [CRITIC] | verdict-row template text; prose
    mentions of the marker word are unaffected.
  • Critic grammar: UPHELD is now truthful at LOW and INFO (rank equality with
    the reviewer's severity is still enforced, and UPHELD at NONE stays
    rejected); a CONFIRMED reviewer row with severity NONE is now rejected with a
    field-level error; the contract card states the UPHELD/DOWNGRADED severity
    rules; the critic lane's controller block renders each assigned item's
    reviewer severity.
  • PR critic dispatch now requires the critic agent (its prompt carries a
    PR-contract override suspending the PLAN REVIEW structure when the controller
    block is present); critic_finding_validator and the other critic-family
    roles are rejected with a message naming the right agent.
  • Council explorer lanes get a council-specific output contract: no structured
    settlement guidance (the settlement tool does not accept council lanes) and a
    plain-marker-rows response-format override of the agent's fenced-JSON
    default; the suffix is recognized on re-prompt (idempotent).
  • The swarm-pr-review skill no longer contradicts the controller: Profile A
    template fences are content-only with a separately headed Profile B/C row
    formats section (full-grammar rows behind an echo-hostile
    EXAMPLE — NOT A LIVE ROW: prefix), the "final line"/"planning preamble"
    rules are replaced by the rows-anywhere contract, the collection-deadline
    wording matches the observer semantics of issue #2381, and the reviewer row
    arity is documented as 12-field with legacy 10-field normalization.

Why

Issue #3310 (Workstream K PR 1 of 4): a PR-review lane that obeyed its Stage-B
system prompt, echoed an operator-supplied row template, or agreed with a
LOW/INFO reviewer finding could not produce a settleable artifact — the
exact-set parser voided the whole lane, the critic transport had no truthful
UPHELD row, and the skill text taught rules the controller does not enforce
(#3275 RC1/RC2/RC4).