Skip to content

feat: sync upstream workflow and recovery improvements - #9

Merged
tiago-peixoto merged 9 commits into
mainfrom
fm/fork-upstream-sync-aug17
Aug 17, 2026
Merged

tiago-peixoto merged 9 commits into
mainfrom
fm/fork-upstream-sync-aug17

Conversation

@tiago-peixoto

Copy link
Copy Markdown
Owner

Intent

Perform the ordinary daily official-upstream synchronization into fork main for the seven commits e518906..bdae21e that upstream gained while the previous fork-main pull request awaited merge, so the divergence ledger can then be populated. This is a required precondition, not housekeeping: bin/fm-fork-topic.sh line 223 refuses to add any divergence topic unless upstream is already an ancestor of fork main, so registration of the two carried units is blocked until this lands. IMPORTANT DELIVERY REQUIREMENT: merge this pull request with the REGULAR merge method, never squash and never rebase. The two-parent merge is the deliverable; squashing it silently discards the ancestry exactly as it did on pull request 7, which then had to be redone as pull request 8. Captain-set constraints carried forward: follow the fork-main-integration upstream-integration procedure exactly, using an isolated candidate from the private fork integration clone with fm-fork-merge.sh prepare and continue, and validate through the isolated fork-target registration; never force-push or rewrite published history; never allow an upstream integration to happen implicitly as an unreviewed side effect; the captain merges and this pull request still requires explicit captain approval. Two conflicts arose and both needed the two sides combined rather than one side picked. In CONTRIBUTING.md, upstream's CLAUDE.md sentence is taken because upstream replaced the symlink with a real AGENTS.md pointer file and the merged tree genuinely carries that pointer blob-identical to upstream's, so the fork's older symlink wording would now be false, while the fork's following line is kept because pointing at AGENTS.md as the single owner of the shared tracked-material list is exactly the fork divergence, replacing an inline list upstream cannot keep current for a fork that adds its own tracked manifest. In the README command table, the fork's updatefirstmate row is kept because it describes permanent-fork topology validation and separate upstream-integration reporting that only the fork performs, while upstream's stow row is taken because upstream added open-record persistence to that command and the fork should not carry a stale description of shared behavior. No fork behavior was dropped and no upstream guarantee was weakened.

What Changed

  • Merge seven official upstream commits through bdae21e with two-parent ancestry preserved, and record the synchronized range in the fork divergence ledger.
  • Persist open work records during /stow, keep Pi export confirmations visible, and preserve Relay promised-final follow-ups for secondmate-routed work.
  • Consolidate keyed decision answers into the shared hold lifecycle and replace the CLAUDE.md symlink with an @AGENTS.md pointer file, with corresponding CI, documentation, and regression coverage updates.

Risk Assessment

🚨 High: Captain, the merge topology and conflict resolutions match the stated intent, but the imported answer-time closure can violate the existing routed-work invariant and needs explicit approval before merge.

Testing

Focused fork-integration and pointer tests passed; actual-target topology, ancestry, ledger, conflict resolutions, and health were verified through a disposable validated fork/upstream topology after the gate checkout’s missing upstream remote prevented an in-place health run, two reviewer-visible transcripts were captured, and the worktree was left clean.

Evidence: Upstream sync validation transcript
Fork upstream synchronization validation

Merge topology
target: 86df18c97a02421e3baf8003353476458293a208
parents: 18e95c7c3a5c10465aba560a8ee6ec996d4ad6b4 bdae21ed09d2cca4f57caed4bda9d30d8f9d9be8
subject: Merge upstream/main into fork main
verified: target has exactly two parents in fork-first/upstream-second order

Incoming official-upstream range
previous upstream: f1a4af426d7199c1781bc91ccd143b8e1f732d10
incoming upstream: bdae21ed09d2cca4f57caed4bda9d30d8f9d9be8
commit count: 7
  7a3259e fix: keep the public promise reachable when work is routed to a second mate (#2457)
  196fb65 docs(skills): add remote-secondmate recovery hint for false-negative verdicts (#2456)
  ef35d79 fix(calm): keep Pi's export confirmation visible (#2461)
  e518906 feat(stow): add open-record persistence to /stow before reset (#2488)
  362c508 fix(decisions): close decision holds at answer time via one general keyed-answer path (#2490)
  4913723 fix(memory): emit a real @AGENTS.md pointer instead of a CLAUDE.md symlink (#2512)
  bdae21e fix(ci): keep CLAUDE.md pointer check valid (#2515)

Divergence-registration ancestry precondition
before target: blocked (official upstream is not an ancestor; merge-base exit 1)
after target: eligible (official upstream is an ancestor; merge-base exit 0)
    || die "manifest records $ID as accepted upstream and retired; choose a new id"
  TOPIC_REF=$(fm_fork_topic_ref "$REPO" "$TOPIC") || die "canonical topic is missing: $TOPIC"
  git -C "$REPO" merge-base --is-ancestor "$UPSTREAM_REF" "$ORIGIN_REF" \
    || die "official upstream must be integrated and validated before adding a divergence topic"
  git -C "$REPO" merge-base --is-ancestor "$UPSTREAM_REF" "$TOPIC_REF" \

Recorded upstream-sync ledger entry
{
  "date": "2026-08-17",
  "fork_before": "18e95c7c3a5c10465aba560a8ee6ec996d4ad6b4",
  "upstream_before": "f1a4af426d7199c1781bc91ccd143b8e1f732d10",
  "upstream_after": "bdae21ed09d2cca4f57caed4bda9d30d8f9d9be8",
  "touched": [],
  "validation_pr": null
}

Conflict-resolution outcomes
CONTRIBUTING pointer sentence: target matches upstream
  `AGENTS.md` is the agent's main job description and names when to load bundled firstmate skills; `CLAUDE.md` is a real `@AGENTS.md` pointer to it, and `.claude/skills` is a symlink to `.agents/skills`.
CONTRIBUTING ownership sentence: target matches fork
- [`AGENTS.md`](AGENTS.md#1-identity-and-prime-directives) is the single owner of Firstmate's shared tracked-material list.
README updatefirstmate row: target matches fork
| `/updatefirstmate` | Fast-forward the running firstmate and its secondmates from origin, validate permanent-fork topology, report any separate upstream integration need, then re-read instructions and nudge updated secondmates |
README stow row: target matches upstream
| `/stow`            | Sweep the session for uncaptured durable knowledge, persist the open work records this session knows are unfiled or now wrong, curate tiered startup memory with decay and cold archival, enforce each home's budget or surface the required decision, cascade to registered second mates, and report what is safe to reset |
CLAUDE.md pointer entry: target is blob-identical to upstream
100644 blob a9d4d2694af261cf23ed331d4b446bdec194c47b	CLAUDE.md
<!-- Points Claude at AGENTS.md via import; edit AGENTS.md, not this file. -->
@AGENTS.md

Actual target health through disposable validated topology
Fork divergence health: retained=0 patches=0 not-upstream=5 integration-artifacts=5 retired-history-patches=0 accepted-upstream-patches=0 trend=unchanged superseded=0 signals=5 errors=0
Refs: fork=86df18c97a02421e3baf8003353476458293a208@86df18c9 upstream=bdae21ed09d2cca4f57caed4bda9d30d8f9d9be8@bdae21ed
Classes: pending=0 rejected-but-retained=0 private=0 superseded=0
Oldest pending: none
Last upstream merge touched: 0
Accepted upstream and retired: 0
Manifest/Git signals (informational):
  - non-upstream commit fe30ee2e2ccf678bba877659e47bae71318a5fab is not represented by a canonical manifest topic
  - non-upstream commit 067881f8a67c9270edfb621784f7cf530fd38ccf is not represented by a canonical manifest topic
  - non-upstream commit 2def68de4882b16f3c5160dc44616a650606b321 is not represented by a canonical manifest topic
  - non-upstream commit d0a5f5a3f72a36e833b5ace9229d8bad8d1c407f is not represented by a canonical manifest topic
  - non-upstream commit 00e17f65aa3173ab9d3a3824565609baba569db6 is an integration-path artifact after cb61bd3364cdc8e2d0ef04fcad8096664bc1df60, not a carried divergence

verified: actual target health returned exit 0 with errors=0
Evidence: Two-conflict reconstruction and combined resolutions
Reconstructed upstream-merge conflicts
conflict count: 2
paths:
CONTRIBUTING.md
README.md

target: 86df18c97a02421e3baf8003353476458293a208
parents: 18e95c7c3a5c10465aba560a8ee6ec996d4ad6b4 bdae21ed09d2cca4f57caed4bda9d30d8f9d9be8
subject: Merge upstream/main into fork main


diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
remerge CONFLICT (content): Merge conflict in CONTRIBUTING.md
index 2ae7efa..4e82b9c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -35,13 +35,8 @@ See the [no-mistakes quick start](https://kunchenguid.github.io/no-mistakes/star
 ## Repo conventions
 
 - This repo is a template for running a firstmate orchestrator agent.
-<<<<<<< 18e95c7 (Merge pull request #8 from tiago-peixoto/fm/fork-upstream-ancestry-redo)
-  `AGENTS.md` is the agent's main job description and names when to load bundled firstmate skills; `CLAUDE.md` is a symlink to it, and `.claude/skills` is a symlink to `.agents/skills`.
-- [`AGENTS.md`](AGENTS.md#1-identity-and-prime-directives) is the single owner of Firstmate's shared tracked-material list.
-=======
   `AGENTS.md` is the agent's main job description and names when to load bundled firstmate skills; `CLAUDE.md` is a real `@AGENTS.md` pointer to it, and `.claude/skills` is a symlink to `.agents/skills`.
-- Only shared material is tracked: `AGENTS.md`, `README.md`, `CONTRIBUTING.md`, `.tasks.toml`, `.github/workflows/`, `bin/`, `.agents/skills/`, and `skills/`.
->>>>>>> bdae21e (fix(ci): keep CLAUDE.md pointer check valid (#2515))
+- [`AGENTS.md`](AGENTS.md#1-identity-and-prime-directives) is the single owner of Firstmate's shared tracked-material list.
   `.agents/skills/` holds agent-loaded skills that assume a live firstmate home and carry `metadata.internal: true` so installers such as [skills.sh](https://skills.sh) hide them from discovery; `skills/` holds standalone, installer-facing public skills with no firstmate dependency (see the README's "Two-tier skill layout").
   Everything personal to one captain's fleet (`.env`, `data/`, `state/`, `config/`, `projects/`, `.no-mistakes/`) is gitignored; never commit it.
   The root `.tasks.toml` is tracked `tasks-axi` config for `data/backlog.md`; compatible `tasks-axi` is the default backend for routine backlog mutations, with the compatibility definition owned by [`docs/configuration.md`](docs/configuration.md) ("Backlog backend").
diff --git a/README.md b/README.md
remerge CONFLICT (content): Merge conflict in README.md
index 56ef4a7..5de5d57 100644
--- a/README.md
+++ b/README.md
@@ -175,13 +175,8 @@ Claude and grok use the slash form shown here; codex uses the same names with `$
 | `/afk`             | Enter away-mode supervision: the sub-supervisor self-handles routine notifications in bash, escalates captain-relevant events and bounded declared-external-wait rechecks as batched digests, and actively alerts if delivery gets stuck while you step away |
 | `/ahoy`            | Recap visible session events since the prior real captain message plus visibly unanswered captain decisions, then guide the captain through any open decisions one at a time in agent-judged impact order; fall back to Bearings when invoked as the session's first real captain message |
 | `/bearings`        | Generate a concise four-section chat digest from bounded local fleet and registered-secondmate state; use `/bearings file` to also replace today's dated report in `data/`, and add `include PRs` when live PR enrichment is wanted |
-<<<<<<< 18e95c7 (Merge pull request #8 from tiago-peixoto/fm/fork-upstream-ancestry-redo)
 | `/updatefirstmate` | Fast-forward the running firstmate and its secondmates from origin, validate permanent-fork topology, report any separate upstream integration need, then re-read instructions and nudge updated secondmates |
-| `/stow`            | Sweep the session for uncaptured durable knowledge, curate tiered startup memory with decay and cold archival, enforce each home's budget or surface the required decision, cascade to registered second mates, and report what is safe to reset |
-=======
-| `/updatefirstmate` | Self-update the running firstmate and its secondmates to the latest from origin with fast-forward-only pulls, then re-read instructions and nudge secondmates |
 | `/stow`            | Sweep the session for uncaptured durable knowledge, persist the open work records this session knows are unfiled or now wrong, curate tiered startup memory with decay and cold archival, enforce each home's budget or surface the required decision, cascade to registered second mates, and report what is safe to reset |
->>>>>>> bdae21e (fix(ci): keep CLAUDE.md pointer check valid (#2515))
 
 Bearings invocation examples:
 

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 4 issues (1 error, 3 warnings)
  • 🚨 bin/fm-decision-hold.sh:779 - Every captured keyed choice is passed to the unrouted answer path, which records (none) and closes the hold. This conflicts with the lifecycle invariant that an answer authorizing follow-up work remains open until dependent work is created and resolve records the routing; normally that semantic decision happens only after the handler reads the captured answer. Decide whether losing that routed-work audit link is intentional; otherwise persist the answer without closing until the handler selects resolve, decline, or answer.
  • ⚠️ bin/fm-procevent.sh:416 - Keyed answers are fed only immediately after fresh capture. A crash after durable capture at line 409 but before this call leaves the result eligible for re-announcement, yet reconcile only republishes pending results and never retries the idempotent answer feed. Replay answer feeding from the pending-result reconciliation boundary so the exact captured-answer/open-hold failure cannot recur after a restart.
  • ⚠️ bin/fm-procevent.sh:713 - Decision bindings are removed only by explicit cmd_retire; automatic terminal retirement uses retire_owned_terminal_source and bypasses this cleanup. A Lavish Send & End therefore leaves its binding indefinitely, and rearming the same artifact reuses the same source ID and can route later choices to the old origin. Retire the binding at a shared terminal-result lifecycle boundary that still preserves pending-result replay.
  • ⚠️ bin/fm-decision-hold.sh:724 - command_unbind removes through state/decision-bindings without verifying that the parent directory is a real directory rather than a symlink. A symlinked binding directory makes source retirement delete &lt;source&gt;.origin outside Firstmate state; read_binding can similarly read through it. Apply the same parent-directory safety check used by command_bind before reading or deleting bindings.
✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-fork-main.test.sh
  • bash tests/fm-ensure-agents-md.test.sh
  • git merge-base --is-ancestor bdae21e… 18e95c7… and git merge-base --is-ancestor bdae21e… 86df18c… to verify the guard changes from blocked to eligible
  • git rev-list --no-merges --count f1a4af4…..bdae21e… and ordered commit inspection
  • git show --remerge-diff 86df18c… -- CONTRIBUTING.md README.md with side-specific line and CLAUDE.md blob comparisons
  • jq validation of .upstream_syncs[-1] in fork-divergences.json
  • bin/fm-fork-status.sh --repo &#34;$PWD&#34; --fork-ref 86df18c… --upstream-ref bdae21e… --facts-only (in-place setup probe; unavailable because the gate checkout has no upstream remote)
  • bin/fm-fork-status.sh --repo <disposable-validated-clone> --fork-ref 86df18c… --upstream-ref bdae21e… --facts-only
  • git status --short --untracked-files=all and evidence-file existence checks
⚠️ **Document** - 1 warning
  • ⚠️ docs/configuration.md:393 - The imported contract says every secondmate-routed Relay request uses the typed promised-final path, but docs/remote-secondmates.md says remote routes cannot carry delegated public replies, and brief emits a main-home path with no cross-host transport. Decide whether to scope the promise to local secondmates or add remote result transport; adding transport best preserves upstream’s stated guarantee.
✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

kunchenguid and others added 9 commits August 15, 2026 22:10
…d mate (kunchenguid#2457)

The lightweight Relay follow-up link lives in the answering home's own
state/<task-id>.meta, so it can only bind work that home owns. When a
Relay-linked request is routed to a second mate, the task record lives in the
second mate's home, fm-x-link.sh failed with a bare "no such task ...meta", and
nothing else picked the promise up: only the soft acknowledgement was ever
posted. The typed promised-final path already supports --work-home
secondmate:<id>; the playbook simply never chose it.

- fmx-respond now states the routing rule crisply: a task in this home takes the
  lightweight link, and second-mate-routed work takes a promised-final
  commitment bound to that home, registered up front with the brief command
  carried into the routed worker's instructions.
- fm-x-link.sh refuses a task with no local record by naming the registered
  second mate whose home actually holds it and printing the promised-final
  registration command, with the exact --work-home when the match is
  unambiguous. A home with no registered second mates keeps the plain error.
- fm-backlog-handoff.sh reports, after a successful move, any moved key that
  still owes a public reply bound to main/<key>, since that binding no longer
  names the home owning the work. The move itself is never blocked.

Docs and the secondmate handoff prose follow the same rule. Tests cover the
refusal, its scoping, the unchanged local-link path, and both handoff outcomes
at the script boundary.
…verdicts (kunchenguid#2456)

* fix(skills): hint that remote secondmate liveness verdicts false-negative

fm-crew-state and fm-send routinely misreport a live remote secondmate
as dead; confirm against the pane before relaunching, and relaunch only
through fm-spawn.sh, never raw herdr pane surgery.

* no-mistakes: apply CI fixes
Pi 0.83.0 added a status line to every tool-expansion change, and Pi
updates the previous status line in place when two status messages
arrive back to back. Calm's post-export redraw cycled tool expansion on
the macrotask right after Pi printed "Session exported to: <path>", so
both expansion status lines coalesced over that confirmation and the
captain was left with no record of where their export landed.

Calm now repaints only the tool rows it presents, by invalidating each
row through the render context Pi hands its render slots, and requests
the surrounding redraw through setStatus. Neither appends to the
transcript. The repaint is still needed because Pi can re-render a row
asynchronously - the built-in edit row invalidates itself once its diff
is ready - and that re-render can land inside the window where /export
forces stock rendering.

The real-terminal /export case now asserts the confirmation is still on
screen after the redraw has settled, and that the redraw restored every
Calm-hidden row, instead of only racing the moment the confirmation
first appeared.
…nguid#2488)

* feat(stow): persist the open records a session is holding

/stow curated memory and captured session knowledge, but never touched
record state, while AGENTS.md called it an "unfinished-work sweep" and the
receipt declared the session "safe to reset" - wording that implied a
record-correctness guarantee stow does not make. A shipped PR with no
backlog item, a queued umbrella whose phases had merged, and four decision
holds left open after their answers shipped all survived repeated stows.

Add a bounded pass that files record state from the same volatile input the
rest of stow already uses: the open threads in context, minutes before the
reset destroys them. It creates a record for an unfiled thread and corrects
one the session knows is wrong, through the owning path, and states its
boundary as part of the contract - it never enumerates the backlog, lists
holds, or queries a forge, because it cannot be a reconciliation and must
not be read as one.

Correct the wording in AGENTS.md and the completion receipt so reset-safe
means what it actually guarantees: nothing this session knew was lost.

* no-mistakes(review): correct stow decision-hold inspection to read hold via tasks-axi

* no-mistakes(document): note /stow open-record persistence in README command catalog

* refactor(stow): state open-record persistence as principle, not procedure

The first version enumerated triggers, named commands, and prescribed an
ordered procedure. That is too rigid for an agent skill: it invites literal
execution of a checklist instead of judgment, and every enumerated example
is a way for the guidance to go stale.

Reduce it to the intent - before a reset, the important open work you are
holding in context must end up durably recorded rather than dying with the
session, filing what is unfiled and correcting what is stale - and let the
agent judge importance, the record, and the owning write path.

Keep the scope bound, since it is a decided contract and not a mechanic:
this covers the open work the session is holding, never a reconciliation of
durable records against repository or forge reality. The wording
corrections in AGENTS.md and the completion receipt are unchanged.
…eyed-answer path (kunchenguid#2490)

* fix(decisions): close captain holds at answer time

Firstmate had two "a decision is open" ledgers with asymmetric closing
mechanics. The live status-log ledger closes atomically at answer time,
because bin/fm-send.sh --resolve-key makes answering a decision be the
act that closes it. The durable backlog hold ledger had no such coupling:
answering and recording were two separate acts, and only the first was
forced by the workflow.

That asymmetry lost four real captain decisions. Their answers were
captured durably to disk, keyed character for character by the hold
decision keys, acknowledged, and even implemented and shipped, yet the
holds stayed open for two days and the captain was asked to re-answer
decisions already on his own disk.

Give the hold ledger the same answer-time-closure property:

- bin/fm-decision-hold.sh gains an `answer` subcommand, the hold ledger's
  counterpart to --resolve-key. It shares one unrouted close
  implementation with `decline`, so it carries every existing guard - the
  captain decision file, the active-hold requirement, retry identity, and
  the refusal to release still-routed work - and differs only in the
  resolution mode it records. `decline` keeps its stronger meaning that
  the answer routes no follow-up work at all.
- bin/fm-procevent-lavish.sh wires the channel that actually carried the
  lost answers. `arm --decisions-origin` binds a deck to the origin whose
  holds it carries, `answers` reads the structured choices out of a
  captured poll result, `close-decisions` maps each key to its hold and
  closes it through the command above, and `autohandle` lets the runner
  apply that at capture time.

Safety is preserved rather than traded away. Only rows tagged `choice`
are read, so freeform captain prose cannot forge a decision key. Closure
is confined to the one bound origin. The decision text is a pure function
of the captured result, so a replayed capture is idempotent. A hold that
is absent, already closed, or still blocking routed work is skipped and
left for `resolve`, never forced. A deck armed without the binding
touches no hold at all. And autohandle deliberately never reports full
handling, because recording an answer is transcription while acting on it
is firstmate's judgement - so the check wake still reaches the handler.

fm-send --resolve-key is untouched.

* no-mistakes(document): document state/lavish-decisions binding dir in AGENTS.md state inventory

* refactor(decisions): make keyed-answer closure one general capability

The previous pass gave holds answer-time closure but built it as bespoke
Lavish wiring: the review adapter carried the source-to-origin binding,
mapped keys to hold identities, wrote decision records, decided what to
skip, and closed holds itself. That treated a review deck as a special
decision source. It is not - it is an ephemeral discussion format that
happens to carry answers.

Collapse it into ONE general capability with one owner.

bin/fm-decision-hold.sh now owns the whole of "a keyed answer closes its
matching hold":
- `answers <origin> --source <provenance>` is the channel-agnostic
  intake. It reads key/answer/label lines on stdin, maps each key to its
  hold, and closes it through the same `answer` path, so every guard
  applies identically whatever channel the answer came from. --source is
  provenance recorded in the decision, never a behavior switch; there is
  no per-channel branch and no knowledge of chat, decks, or transports.
- `bind`/`unbind`/`binding` own the source-to-origin binding for any
  channel whose answers arrive detached from their origin.

Every channel is now an ordinary caller that only turns what it received
into keyed lines:
- bin/fm-send.sh (chat) feeds the intake for a key that names an active
  hold. This also fixes a real gap: once `complete` transfers a decision
  to its hold it closes the live status copy, so --resolve-key alone
  could never answer a transferred decision.
- bin/fm-procevent.sh feeds it generically. A bound source's captured
  result goes to `<adapter> answers <result-file>` and whatever that
  prints is piped into the intake. The runner names no adapter, parses
  no result, and carries no decision rule, so any future adapter with an
  `answers` command works with no change here.
- bin/fm-procevent-lavish.sh keeps only `answers`, which reports the
  structured choices a review captured and stops. It maps nothing to a
  hold and closes nothing; it lost ~160 lines of decision logic.

Feeding is independent of handling, so it never acknowledges a result
and never suppresses a wake - recording an answer is transcription,
acting on it stays firstmate's judgement.

The regression that proves closure now drives a FIXTURE adapter that is
not the review adapter, so what is proven is that any bound channel
reaches the intake rather than that one channel is wired specially. A
new regression drives the real fm-send over a stubbed transport for the
chat side. Every prior guarantee still holds, and fm-send's status-log
behavior is unchanged.

* no-mistakes(review): test(decisions): drop source-content grep from hold-closure regression
…mlink (kunchenguid#2512)

A Write aimed at CLAUDE.md followed the symlink and destroyed AGENTS.md.
The installer now creates and migrates to a recoverable two-line pointer file.
@tiago-peixoto
tiago-peixoto merged commit 1e24af6 into main Aug 17, 2026
13 checks passed
@tiago-peixoto
tiago-peixoto deleted the fm/fork-upstream-sync-aug17 branch September 4, 2026 21:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants