Skip to content

feat(work): pipeline edges, spend confirmation, and a visible finish line - #83

Open
jiunbae wants to merge 23 commits into
mainfrom
feat/work-control-plane
Open

feat(work): pipeline edges, spend confirmation, and a visible finish line#83
jiunbae wants to merge 23 commits into
mainfrom
feat/work-control-plane

Conversation

@jiunbae

@jiunbae jiunbae commented Aug 25, 2026

Copy link
Copy Markdown
Member

Turns muxa work up into a pipeline that can actually finish: agents wait on
each other, hand off through commits, and — the part that was missing — say so
where an operator can see it.

Why

A run of the pair pipeline (implementer → reviewer) produced a good review that
then sat unnoticed for two hours. Nothing was broken in a way that showed up:
the review completed, both panes went idle, and muxa work up rendered exactly
what it renders for a pipeline that stalled. Several separate defects fed into
that, and each is a commit here.

What it adds

Edges. after = ['impl'] holds an agent until its upstream reports
muxa work done, so a reviewer never reads a tree that is still moving. The
graph is cycle-checked and drawn in the plan output, because the TOML cannot be
read as one.

Spend confirmation. A ticket lookup costs a headless turn. work up now
names what it will call and waits for a yes before spending, --dry-run
included — a dry run that writes config still bills on the next real run.

muxa work init. Describes a pipeline in your own words and has an agent
write the [ticket]/[[route]]/[pipeline.*] config, with a drift test that
fails if a config field is missing from the schema the agent is handed.

Completion, everywhere. work list renders as a table with a DONE
column; watch shows the ratio on the work row and names panes by their
pipeline alias (impl, review) rather than by CLI, which stops
distinguishing anything when one work runs two claudes. w opens a composer
and hands you to a window running muxa work up, so the confirmation flow
stays in one place rather than being rebuilt inside a TUI.

Detection fixes

Codex's trust gate paints before the session that owns its hooks exists, so no
hook can report it. Four separate things had to be true for muxa to see it, and
none were: the manifest was selected by pane_current_command (an npm codex is
node), a hook-owned pane skipped capture entirely, a pane with no registry row
could not be identified at all, and prepare_capture sliced the tail of a
50-row pane whose only content was in the first nine lines.

role: routing was also silently dropping messages. work up stamps
@muxa_agent_role on every pane it launches, but the collaboration room only
knew about identities an agent registered for itself — which a pipeline agent
never does. PANE_FMT had been fetching that option all along and the parser
threw it away.

Verification

Ran end to end against a real Linear ticket, from an empty workspace: w
spend gate → callabo-set ws create → implementer commits and reports →
reviewer launches on the fixed commit → findings handed over → applied and
committed → re-review returns converged → both report done → 2/2.

The first such run needed three manual rescues. The last one needed none.

cargo fmt --all -- --check, cargo clippy --workspace --all-targets -D warnings, and 1579 tests pass on this branch rebased onto main.

One commit from the branch was dropped as empty during that rebase — prepare
support landed independently via #82, identically enough that git found nothing
left to apply.

🤖 Generated with Claude Code

Jiun Bae and others added 23 commits August 25, 2026 18:10
`[ticket]`, `[[route]]`, and `[pipeline.*]` are the most structured
configuration muxa asks for and the least guessable: nested tables, an
ordered array of routes, regexes, placeholder templates. Reading a
reference and hand-editing `config.toml` is the friction that stops the
feature being used at all.

So muxa does here what it already does for tickets — it asks an agent.
One headless turn (`ask::one_shot`, the same bridge) turns "cal-* tickets:
codex planner, codex implementer, claude reviewer" into TOML.

The point of the command is the part it does *not* delegate. `Config`
denies unknown fields, so a model's typo written to disk takes the daemon
down at its next start. `validate_proposal` parses the answer, compiles
every regex, checks each route names a pipeline that exists, and rejects
an agent whose `program` is not one of the four allowlisted CLIs — naming
the offending value and what is allowed. Nothing is written until it
passes and the operator confirms.

Merging goes through `toml_edit` and touches only those three keys, so
comments and every other section survive verbatim; the command reports
which of the three it would replace rather than half-merging a pipeline
set. Writing reuses `init::apply::atomic_write`, now shared, so the file
is never seen truncated.

Verified end to end against a fake resolver: a good proposal writes,
`[watch]`/`[ask]`/`# my notes` survive, and the result immediately drives
`muxa work up cal-42 --dry-run` into the generated triad. A proposal
naming `program = 'vim'` is refused with that reason and the file's
checksum is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`muxa work init` printed "asking claude to write the config…" and was
already spending the turn. Worse, `--dry-run` reads as "nothing happens"
when it skips only the file write: the agent turn — the expensive half —
runs either way and bills the same.

Before spawning, both commands now name what they are about to run: the
agent, the directory it runs in, its permission policy and timeout, and
that it is billed like any other turn. Under `--dry-run`, `work init`
says outright that the file write is the only thing being skipped, and
`work up --help` says ticket lookup still costs a turn on a cache miss.

`work init` asks before spending, and a non-interactive caller must pass
`--yes` — the same gate the rest of muxa puts in front of an action you
cannot take back. Refusing happens before the spawn, not after.

Both then report what the turn actually cost, which `AskAnswer` already
carried and nothing surfaced.

`work up` gets the notice but no prompt: it is the everyday command, the
15-minute cache makes re-runs free, and announcing only on a cache miss
keeps the common path quiet. `--no-ticket` spends nothing and says
nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The confirmation was yes/no, so the only way to resolve with codex
instead of claude was to remember `--agent` before running. Picking the
agent is already the decision to spend, so it is now one prompt:

    Spend one agent turn on…
      ● claude  configured
      ○ codex
      ○ Cancel  call nothing

The list is derived, not written down: agents the headless bridge can
actually drive, intersected with what is on PATH. `muxa::ask` now names
that set, so it stays one source of truth and a new resolver shows up
here for free.

That is deliberately narrower than the agents muxa can launch into a
pane. The launcher knows gemini, agy, and opencode; the bridge needs a
print mode that reports completion as a fact — an exit code plus a
parseable envelope — and only claude and codex have one it parses today.
Offering an agent that fails after being picked is worse than not
offering it. Checked while writing this: gemini's `-p -o json` is the
right shape but this machine's CLI now refuses to authenticate at all
(Google moved individuals to Antigravity), and `agy` is not installed
here, so neither could be added and verified rather than guessed.

Nothing on PATH is a clear refusal naming what muxa needs, and a
non-interactive run still stops before spawning anything.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`work up` decided staffing from tmux alone: does this alias have a pane.
muxa has tracked agent state all along — Working, Idle, WaitingInput,
WaitingChoice, Error — and the plan never asked. So three very different
situations read as one identical `= running`: an agent eleven minutes
into the work, one that finished its turn hours ago, and one frozen on a
permission dialog nobody will ever answer.

The plan now carries state from the daemon's registry. An agent blocked
on a person becomes its own step and is never prompted over: a message
typed at an approval dialog is swallowed by the dialog, and the operator
is left believing it was delivered. That also means such a window is not
reported as converged, because it cannot converge until somebody answers
it.

Reading state is best-effort. `work up` does not otherwise need the
daemon and a launch must not fail because the control plane is down; with
no snapshot the plan degrades to the pane-only view it always had.

`--show-prompts` prints the exact text each agent would receive. It was
already in `--dry-run --json`, buried where nobody reads it, which is why
a stale `{{ticket.title}}` could reach a real launch. Seeing the prompt is
the cheapest possible review of a pipeline, and it costs nothing.

`work up` with no --body/--skill/--context now asks for one. muxa keeps
no opinion about what goes in it: "cal-7210 callabo resolve" is opaque
text it carries to every agent verbatim, so invoking a personal skill
stays the operator's business and never becomes muxa's configuration.
Empty is a valid answer, and a non-interactive run is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A first `muxa work up cal-7210` answered with TOML syntax:

    Error: no [[route]] matches work id "CAL-7210";
           add a catch-all route with match = '.*'

That is only an error for someone who already has a config. For everyone
else it is the first thing muxa ever says to them, and telling a new user
to go hand-edit nested tables is how a feature goes unused.

`muxa work init` is already the conversation that fixes it, so the
unconfigured case now hands over to it and retries once with the config
it wrote. One command, one question, and the work starts — no config file
to prepare beforehand.

Only for a missing route, and only interactively: a script wants a
failure it can read, not a prompt, so non-interactive callers keep the
plain error unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…from

`prepare` shipped without being added to the schema `muxa work init` shows
the model, and the consequence was quiet: the model is told not to invent
keys and it obeyed. A real run produced a config with this in it —

    # NOTE: the schema has no `prepare` key (and unknown keys are a hard
    # error), so ... or ask for a real `prepare` hook upstream.

— and worked around the gap by telling the implementer agent to create the
workspace by hand. The guardrail did its job; the schema was wrong.

A key the schema omits is a key that cannot be generated, so the two are
now locked together by serializing the config structs and asserting every
field name appears in the schema text. That test immediately found a
second one: `task` had never been documented either.

Found by running the feature against a real ticket rather than by reading
the code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…host

Two defects a real run surfaced.

**A comment changed which section it belonged to.** `muxa work init`
spliced its tables wherever `toml_edit` put them, which for this config
was between `[dashboard]` and the comment that belonged to it. A comment
before a header lives in the *previous* section's span, so
`# allow_work_start = true` came back sitting inside `[[route]]` — and
uncommenting it there would have been a hard config error, because
`route` has no such key. New sections are now pinned past everything
already in the file, so nothing is ever spliced above an existing
comment.

**A unit test asserted a fact about the developer's machine.** The
dashboard pane scan always includes the host's own tmux server, so
`works.len() == 1` held only while nobody had a muxa-managed work window
of their own. One appeared, and the test started failing for a reason
that had nothing to do with the change under test. It now selects the
work it created instead of counting them, which is what it meant to
assert. This would have failed CI on any machine running muxa.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A codex reviewer pane sat two minutes on

    › 1. Yes, continue
      2. No, quit
      Press enter to continue

while muxa reported it `idle` — indistinguishable from an agent that had
finished its turn. `muxa work up` had just learned to surface a blocked
agent instead of prompting over it, and the signal underneath was wrong,
so it never fired.

There was no bundled codex manifest at all, and the pattern style the
other manifests use would not have matched anyway: they anchor a
selection widget to an ASCII `>` cursor, and codex renders U+203A (`›`).
The new manifest matches `[>›❯]`, and its rules were derived from that
real captured pane rather than guessed.

This closes the case where codex runs as `codex`. Two blockers remain for
installs where it runs under `node`, both in muxad and both needing their
own decision rather than a quiet change here:

- `gather_candidates` selects a manifest by `pane_current_command`, which
  is `node` for an npm-installed codex. The registry already knows the
  pane's agent kind and would be the better key.
- `PaneOwnership::Hooked` skips capture entirely for hook-reporting
  agents. That is right once hooks flow and wrong before the session
  exists — the startup gate precedes every hook, so no hook can report
  it. Codex is not attention-blind the way agy is, so flipping
  `hooks_report_attention` would trade one gap for a regression; the fix
  is a narrower "hooks have not spoken yet" case.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fixing the first host dependence in this test exposed a second one
underneath it. `GET /api/works` is not a read: it upserts an external
item for every pane it discovers, and the pane scan includes the host's
real tmux server. So the store file ends up holding a row per work
running on the developer's machine, and `records().len() == 1` was only
ever true on a machine with none.

It now looks for the record it wrote instead of counting rows, which is
what both assertions were trying to establish.

Worth noting separately: an endpoint spelled GET that writes to durable
storage on every call is surprising on its own terms. That is the v2
work model's design to settle, not something to change quietly from here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…lementer

A pipeline was a *set* of agents, so `work up` always launched all of them
at once. Watching a real ticket showed the cost: the reviewer came up
beside the implementer and spent its rounds rediscovering that the tree
had moved under it — "round 2 handoff 직후 공유 트리에 다른
role:implementer 변경이 덮어써, 코드와 테스트가 다시 불일치". Those
rounds bought nothing; the work was never wrong, the target was.

`after = ['impl']` makes the difference expressible. An agent with an
unmet edge is held back with its own plan step rather than launched, so
the window is honestly not converged, and the next `work up` starts it.
Omit `after` and everything stays parallel, which is right for work that
genuinely is.

The edge opens on an *observed* signal, not an inferred one. muxa cannot
tell "finished" from "paused" — `idle` means both — so the upstream agent
says so with `muxa work done` from its own pane, and muxa records the
claim on the work window. The window, not the pane: an agent that has
finished has often exited, and the fact has to outlive it. It is a claim,
not proof; muxa's job is to make it observable and stop guessing.

Edges gate the start only. Re-gating a live agent would tear down work in
flight every time an upstream alias was reopened.

An edge naming an alias the pipeline does not define, or a cycle, is
refused when the pipeline is read. Both fail silently otherwise: the
dependent agent simply never launches and `work up` keeps reporting a
window that cannot converge without saying why.

Verified end to end: the reviewer is held (`⋯ review waiting after impl`),
`muxa work done` records `done=impl` on the window, the next run launches
the reviewer, and `--undo` puts the edge back.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An array of tables with an `after` key buried in one of them is a poor way
to read a dependency graph. The question an operator has before paying for
a run — what starts now, what waits, what runs together — is exactly the
one the file does not answer.

`muxa work up --graph` lays the pipeline out by dependency depth:

    ── starts immediately ──
     ● docs       codex     writer
     ● impl       claude    implementer
    ── waits for depth 0 ──
     ○ review     codex     reviewer     after impl
    ── waits for depth 1 ──
     ○ ship       claude    releaser     after review, docs

Depth is the point: equal depth means parallel, which no amount of reading
the TOML tells you. `ship` sits at depth 2 because it waits on the deepest
of its edges, not the first one listed.

Layout is a repeated relaxation rather than a recursive walk — the sets are
tiny and this cannot blow the stack on a pathological config — and it
assumes validated edges, which `desired_agents` guarantees by refusing
unknown aliases and cycles before anything renders.

Glyphs stay inside Geometric Shapes U+25A0–25CF. The half-filled circles
just past that range fall back to a different font in most terminals and
render at the wrong size.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two things a real run made obvious.

**The ticket lookup billed before asking.** `muxa work up cal-7210
--dry-run` printed "one headless claude turn, billed to your account" and
"that lookup cost $0.1367" — and only then asked what the agents should
do. The money was gone before the operator had said anything. `work init`
had been fixed to ask first; `work up` was left announcing on the grounds
that it is the everyday command, and that was the wrong call. The cache
makes re-runs free, so the prompt appears exactly when money would move.
`--dry-run` is no exemption: it skips creating panes, not the turn that
resolves the ticket.

The request prompt now comes first too, since it is free and it is the
last thing the operator can still change once spending starts.
Non-interactive callers get a refusal naming both ways through, `--yes`
and `--no-ticket`, instead of a silent charge.

**The graph and the plan printed as two blocks**, so every agent appeared
twice — once as a shape, once as a row — and neither view was complete.
They are the same thing: what runs when, and where it is now. The plan is
now grouped by dependency depth, and `--graph` is gone because there is
nothing left for it to show. A pipeline with no edges stays a flat list
rather than gaining ceremony it does not need.

    now
     ● impl     claude  implementer  will start
    then · after impl
     ○ review   codex   reviewer     waiting   for impl

`UpResult` carries the layout, so the dashboard can draw the same graph
without re-deriving it from config.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
muxa refused to touch a session it had not created, so a `callabo` session
the operator already had got a `callabo-2` beside it. That contradicts the
model it is built on: one session is one workspace, and this split one
workspace across two sessions.

The safety that mattered was never "do not touch it". It is that muxa must
not *close* a session full of windows somebody else opened. So identity and
ownership are now separate facts on the session: an adopted session gets
`@muxa_workspace_id` so its work windows are findable, and never
`@muxa_managed_workspace`, which is the flag `close_workspace` reads. That
call now refuses an adopted session by name and points at closing the work
windows instead.

A workspace id alone is enough to be listed, so `muxa work list` and
`work show` see adopted sessions like any other. Only the destructive path
asks about ownership, which is the only place the distinction should
matter.

Verified against a live tmux: with an unmanaged `cwdtest` session present,
`work up` put the window in it instead of creating `cwdtest-2`, the session
carries `ws=cwdtest` with an empty managed flag, and `workspace close`
refuses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two independent gaps kept muxa reporting `idle` for a codex pane sitting
on its trust/policy question, which is precisely the one screen codex's
own hooks structurally cannot report: it paints before the session that
owns the hooks exists.

1. Manifest selection keyed off `pane_current_command`. An npm-installed
   codex runs as the `node` shim, which names no manifest, so the pane
   was never even a detection candidate. Select by the registry's agent
   kind first (`AgentKind::screen_manifest_name`), falling back to the
   command. The kind only wins where the command still looks like an
   agent host — a direct manifest match or a known wrapper — so a pane
   that fell back to a shell still drops out of candidacy and the
   stop-sweep keeps working.

2. `PaneOwnership::Hooked` skipped capture entirely. Correct in steady
   state, wrong in the window before the first hook can fire. A
   hook-reporting row that is still `Idle` within STARTUP_ATTENTION_WINDOW
   of launch is now merely `AttentionBlind`, so the existing refinement
   path may promote it to waiting — and only that, since `Working`
   contributes nothing there and hooks are never raced.

Both bounds are tested: the window closes on time, a row that already
left `Idle` is untouchable, and a live hook row outside the window still
skips capture.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e top

Live reproduction of the codex startup gate showed the previous commit was
necessary but not sufficient. Two more links in the chain were broken, and
the second is the one that actually swallowed the signal.

`gather_candidates` could only name an agent muxa already had a registry
row for — but the row is minted by the first hook, and the gate paints
before any hook exists, so during the gate there is no row to consult.
Fall back to `discovery::discover_from_panes`, which already walks the
process tree for wrapper panes and finds the real `codex` binary under the
`node` shim. Registry first (it knows what muxa launched), process tree
second, foreground command last.

`prepare_capture` then discarded what it found. tmux pads a capture out to
the full pane height, and the gate occupies the first nine lines of a
50-row pane, so taking the last 40 lines verbatim kept nothing but blank
padding and the pane classified as unknown. Drop trailing blanks before
slicing: they carry no signal for any rule, and without them the tail
means "the last N lines that exist" for scrolling transcripts and
top-anchored screens alike.

Verified live: with the gate on screen, `muxa status` now shows the pane
as `input` and the row reads waiting_input / "codex is waiting". The
regression test is built from that real capture, and a companion test
pins that a long transcript still keeps its end.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`muxa work up` stamps `@muxa_agent_role` and `@muxa_agent_alias` on every
pane it launches, but the collaboration room only knew about identities an
agent registered for itself with `muxa identity set` — which a pipeline
agent never does, and could not do before its first turn anyway. So
`role:implementer` matched nobody and the reviewer's handoff was dropped
with "no eligible peer", leaving the pair stalled while the work looked
finished.

`PANE_FMT` had been asking tmux for `@muxa_agent_role` all along and the
parser threw the column away, so the fix is mostly to stop discarding it:
keep it (plus the alias, appended to the format) on `PaneInfo`, seed
`participants_from` with both, and stop having `enrich_participants` clear
what it did not set.

That splits identity into two claims of different kinds. A registered
identity is the agent's statement about itself and stays bound to the exact
agent generation that made it, so a later process on the same pane inherits
nothing. Pane metadata is the launcher's declaration about the slot — "this
pane is the pipeline's reviewer" — which remains true for whoever occupies
it. Registration therefore overrides pane metadata (wholesale, so an agent
can shed a launched role), and its absence leaves the launcher's claim
standing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`muxa work up` read the work's completion set to decide what to unblock,
then dropped it. So a pipeline that had converged and one that had stalled
rendered identically — every pane `idle` — and a finished review sat
unnoticed for two hours because nothing on screen distinguished "the
reviewer is done" from "the reviewer has not been asked anything".

Carry the set onto `UpResult` and render a reported agent as `done`.
Gated on the agent actually being at rest: one that reported and was then
re-prompted is working again, and showing `done` over live work is a lie
the operator would act on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`work list` printed one `key=value` line per work, so the eye had to parse
it, and the one fact that mattered most was missing entirely: whether the
pipeline had finished. Render it as a themed table — work, workspace,
agents, done, cwd — with the stage column appearing only when something is
staged, and cwd shortened to `~/…` so the column spends its width on the
part that differs between rows. `--json` is unchanged.

`muxa watch` had the same blind spot at the work row. It now carries the
completion ratio, read from `@muxa_work_done` and the pipeline aliases,
which `PANE_FMT` now retains alongside the role and alias it already
collected — so no extra tmux round trip per refresh.

Both count aliased panes only. `@muxa_work_done` records aliases, so a pane
the operator split by hand could never appear in it, and counting it would
show a converged pair as 2/3 forever. A window with no aliases at all has
no ratio rather than `0/1`: it was never a pipeline and nothing there was
ever asked to report.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`muxa watch` could spawn a single agent into a work window but had no way
to run a pipeline, so starting one meant leaving the TUI for a shell.

`w` opens a one-field composer for a work id and hands the operator to a
new tmux window running `muxa work up <id>`.

Watch deliberately does not run the pipeline itself. `work up` resolves a
ticket, which costs money, and the CLI already owns the flow that says so
and waits for a yes. Re-implementing that inside the TUI would put the
decision to spend the operator's money in two places, and the second one
would be the one nobody reviewed. So the TUI's job ends at putting the
operator in front of the real thing.

The work id reaches a shell, and `normalize_work_id` rejects only tabs,
newlines and over-long input — a quote or a semicolon passes. Both it and
the binary path are single-quote escaped, and a test pins that an id built
to break out arrives as one word.

`w` joins `n` on the help line rather than taking one of its own: the
overlay is clipped by terminal height, and an added row pushes the last
binding off the bottom.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The completion badge only reached the flat work row. `[watch] view` defaults
to the tree, so the surface most people actually look at still could not
tell a converged pipeline from a stalled one — which was the whole point of
adding it.

Compute it once in `topology`, where the panes are already being folded into
windows, and carry it on `WindowNode`. The tree label renders it, and the
flat row now defers to the same function instead of restating the counting
rule, so the two views cannot drift apart on whether a pipeline finished.

`PaneNode` gains the pipeline alias for the same reason the window needs it:
completion is counted over aliases, and a hand-split pane has none.

Verified against a live run: the daemon emits
`completion = {done: 0, total: 1}` for a work window whose one aliased pane
has not reported yet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The tree labelled every pane with its agent CLI, which stops distinguishing
anything the moment one work runs two of the same kind: a pair whose
implementer and reviewer are both claude rendered as two identical rows.

Use the alias the launcher already stamped on the pane — `impl`, `review` —
and fall back to the CLI, then the raw command, for panes nobody aliased.
The CLI stays visible on the row and in the inspector, so this trades a name
that repeats for one that identifies.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`PaneInfo` gained `agent_role`, `agent_alias` and `work_done` on this
branch; `registration_pane` arrived on main while the branch was out. The
rebase produced a tree where neither side was wrong and the combination did
not compile.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant