Skip to content

chore(release): merge v2/main into main for v2.10.0 - #2637

Merged
cliffhall merged 296 commits into
mainfrom
v2/chore/milestone-merge-v2.10.0
Oct 7, 2026
Merged

cliffhall merged 296 commits into
mainfrom
v2/chore/milestone-merge-v2.10.0

Conversation

@cliffhall

@cliffhall cliffhall commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

Closes #2623

Milestone merge of v2/main into main for the v2.10.0 release, which is step 2 of the release skill.

This is a pure merge

No commits of its own, only merge commits:

$ git rev-list HEAD --not origin/main origin/v2/main
1bc81367 chore: merge v2/main into main for the v2.10.0 release
c4f2a259 chore: merge v2/main into main for the v2.10.0 release
2500815e chore: merge v2/main into main for the v2.10.0 release
7d971c32 chore: merge v2/main into main for the v2.10.0 release
#   four merges: the smoke found #2629 (fixed by #2634); this PR's review found
#   #2638 (fixed by #2639), and #2546 + #2640 (fixed by #2641); each was
#   fixed on v2/main and re-merged

$ git rev-parse origin/v2/main^{tree}
87356bc1e9ae7333e0e099c9247cf5c4f76f6834
$ git rev-parse HEAD^{tree}
87356bc1e9ae7333e0e099c9247cf5c4f76f6834

The merged tree is byte-identical to origin/v2/main. The tree reads 2.10.0, bumped on v2/main in #2622 / PR #2627.

Verification

Ledger: https://claude.ai/artifact/KQe73j1A6QcGvnpAXfx2Hb (for maintainer review; one row per closed milestone issue, with what was run and what was observed)

  • npm run local:gate on the final tree passes every stage except smoke:web:firefox. That stage cannot launch Playwright's Firefox on macOS 27 (smoke:web:firefox cannot launch Playwright's Firefox on macOS 27 #2625), so the three Firefox smokes were run from a clean export of this exact tree in the Linux Playwright container instead, and all passed. Storybook (529) was run separately, since the Firefox failure stops the gate chain before that stage.
  • npm run pack:verify: OK (27 files, 5.70 MB unpacked).
  • npm audit: 0 vulnerabilities in all six installs.
  • 41 / 41 shipped milestone issues driven from the production build, through web, CLI, TUI and mcpdo.

Found during the release, fixed on v2/main

CodeQL on this PR: 0 open alerts.

Follow-ups filed for v2.11.0: #2624, #2625, #2628, #2630, #2631, #2632, #2633, #2642, #2643, #2644, #2645, #2646, #2647, #2648, #2649, #2650.

🤖 Generated with Claude Code

BobDickinson and others added 30 commits September 28, 2026 16:55
…imulator

The secure-add behavior case composes a requireAuth+DCR streamable-http
server and measures the full headless auth flow: non-TTY connect exits 0
with the sign-in link, and the agent is expected to relay it and finish
the tools/call once access is granted.

The harness plays the human: cases opting in with autoConsent get a
watcher that polls the shim transcript for /oauth/authorize URLs and
approves each once (GET consent page, POST approve, follow the redirect
to the detached auth helper's loopback callback), after which the
agent's next call revives the connection.

Also: missed samples now dump a compact transcript (argv, exit, first
300 chars of each stream) as the failure diagnostic, and CASE_MATCH
filters cases by prompt substring for one-case iteration (labeled a dev
probe, not a measurement).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
…fixture tool

collect_elicitation can only exercise the elicitation wire: the caller
composes the message and schema, so an agent can trivially satisfy it.
Measuring elicitation BEHAVIOR needs a tool whose elicitation is
intrinsic — the elicited fields deliberately absent from the input
schema, so the only way to a result is answering the mid-call request.

New submit_ticket preset: takes only a summary, elicits contact
name/email over legacy elicitation/create, and returns a ticket id
derived from the accepted content (decline/cancel: ticket not filed).

The helpdesk eval case gives the agent the contact facts in a natural
prompt and asserts the parked tools/call (exit 0, elicitationPending)
followed by an elicitation/respond returning the ticket number. No
harness user-simulator involved — the agent itself is the answerer.

First probe (claude, 3 runs): 3/3 with the current SKILL.md — the
pending output's in-band answer guidance is sufficient.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
SKILL.md body rewrite (description frontmatter unchanged):
- command summary opens with servers/list; canonical flow spelled out
  (servers/list -> connect -> @entry <command>)
- new 'How to think about mcpdo' section: connections extend the toolset,
  answer capability questions with them, don't auto-connect, inspect via
  commands not the filesystem
- new 'The catalog' section: writable ~/.mcp-inspector/mcp.json,
  --catalog / MCP_CATALOG_PATH, servers/* vs connections/*, edit by file
- Conventions: connection self-healing and the [legacy]/[modern] era tag
- 'Auth': non-TTY connect exits 0 with pendingAuth + authUrl; relay the
  link, then retry the command with short sleeps (measured: 'wait for the
  user' wording made agents end their turn — 0/3; retry wording 3/3)
- 'Elicitations': parking + elicitation/respond, decline/cancel/--done,
  TTL and one-parked-call limit

Four regression behavior cases appended to evals.json (each guards a fixed
skill gap): connections-awareness, catalog-discovery, explicit-connection
(two-server catalog, matcher pins the connection), helpdesk-decline
(asserted via stdoutMatch since boolean flags don't surface in parsed
argv). All baselined 3/3 pre-edit; post-edit full suite: trigger 8/8 at
100%, behavior 8/8 at 3/3 incl. secure-add 2/3 -> 3/3.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
- `agent-help` (default, or explicit `--skill`) now prints the SKILL.md
  body with frontmatter stripped, so an agent without the skill installed
  can pull guidance usable exactly as if the skill had loaded.
- `--instructions` prints a short always-on snippet to append to a
  project's CLAUDE.md/AGENTS.md, so agents treat open mcpdo connections
  as part of their toolset on every turn (skills load only on demand).
- `--skill-path` (replaces `--path`) prints the installable SKILL.md
  location for skill runtimes. Flags are mutually exclusive.
- SKILL.md gains a "Make it always-on (optional)" bullet pointing at
  `agent-help --instructions`.
- Test hygiene from ed06480: fix `createStyle` calls to the real
  `(ansi: boolean)` signature and a prefer-const slip; neither changed
  runtime behavior, both now caught by full validate.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
…e artifacts

Harness fixes (both produced silent never-authorized misses):
- Concurrent samples' detached auth helpers all defaulted to callback
  port 6276 and collided (EADDRINUSE, or consent redirected to a dead
  helper). Each sample now sets
  MCP_OAUTH_CALLBACK_URL=http://127.0.0.1:0/oauth/callback — ephemeral
  ports are already supported end-to-end by the product.
- The consent clicker scanned each stream chunk separately, so an
  authorize URL split across chunks was never detected. It now scans
  joined per-stream text (streamText), like the rest of the matchers;
  unit test covers a mid-URL split. Clicks are now logged.

Runner UX:
- AGENT defaults to "all": trigger + behavior suites run for claude and
  copilot back to back (AGENT=claude|copilot still selects one).
- On a behavior miss the sample's hermetic env dir is preserved under
  ~/.cache/mcpdo-skill-eval/failures/ (raw agent stream, shim transcript,
  case, catalog, server configs) and the miss report prints its path.
- Miss diagnostics show per-call timing offsets.

Validated: full AGENT=all suite green — both agents 8/8 trigger @100%
and 8/8 behavior @3/3, including the secure-add OAuth case.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
CI coverage fell below the 90% per-file gates in four files touched by
recent auth/elicitation work. Cover the gaps:

- auth-helper: stdio configs (no marker), non-Error flow failures,
  stdin error/timeout, stdout EPIPE guard, helper spawn failure, and
  noise lines (blank/malformed/unknown events) before the auth URL.
- authorize: non-EMA connect failures rethrown unchanged; caller-provided
  makeNavigation overrides the CLI default navigation.
- format-human: colorLevel warning/debug/notice buckets and empty-URI
  passthrough via formatStreamEventHuman.
- elicitation-park: waitForElicitation with an already-pending frame;
  forClient/cancelForConnection ignoring non-matching entries.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
…er's

Review finding (PR #1783): the behavior evals' shell allow-list looked like
a containment boundary but is not one — approval patterns prefix-match
(`mcpdo x && anything` passes) and `mcpdo connect` launches arbitrary
stdio commands by design. The agent under test is a nondeterministic model
with shell access; its actions are untrusted.

- runPrompt no longer spreads process.env into the agent: agentEnv() picks
  process basics (PATH, HOME, locale, proxies) plus only the agent's own
  auth/config vars (ANTHROPIC_/CLAUDE_ for claude, GITHUB_/GH_/COPILOT_
  for copilot). Exported credentials for anything else stay out.
- The behaviorAgentArgs doc now states the real model: approval scoping is
  drift reduction for a cooperating model, env is minimized, and hard
  isolation is the runner's job (container/VM/dedicated user of choice —
  there is no portable OS sandbox worth shipping here).

Validated: harness unit tests (93) green; live helpdesk 3/3 and secure-add
OAuth 1/1 on both agents under the minimal env.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
Review finding (PR #1783): the accept-path helpdesk case measured only the
call sequence — any summary and any contact details produced
elicitationPending then TCK-. Its stated point is mapping known facts into
the requested schema, so assert them: `summary` on the tools/call and
`contact_name`/`contact_email` on the elicitation/respond. valuesMatch
is spelling-agnostic (key:=value, JSON positional, --tool-args-json all
land in parsed args; plain key=value is rejected by the CLI itself).

Re-baselined 3/3 on both claude and copilot.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
Post-format edit slipped past a re-check before push; format:check:scripts
now clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
Two review findings (PR #1783):

- The pending-marker check and helper spawn were not atomic: two
  concurrent non-TTY connects for the same server could both pass the
  check and spawn helpers that contend for the OAuth callback port.
  A per-server lock file (wx-exclusive create) now reserves the flow
  before spawning; the loser polls for the winner's marker (published
  before the helper reports its URL) and reuses it. Stale locks from a
  crashed reserver are stolen after the URL wait window, so a crash
  cannot wedge sign-in.

- connect's sign-in block emitted the server-controlled OAuth authUrl
  as an OSC 8 hyperlink unconditionally. It now goes through the same
  isSafeLinkTarget scheme allowlist as every other server-supplied
  link; unsafe targets render as plain text.

Validated: full validate green, per-file coverage thresholds met, live
secure-add OAuth eval passing on both claude and copilot.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
…l atomic

Address Copilot review round 3 on the sign-in reservation code:

- waitForPendingAuthUrl: don't unref the poll timer — for a reservation
  loser it can be the only live handle, and an unref'ed timer let Node
  exit mid-wait without ever printing the authorization URL.
- readLivePendingAuthMarker: stop deleting stale/dead markers at read
  time; the unlink-by-pathname raced a just-spawned helper's fresh
  marker (TOCTOU). Stale markers are inert and writers replace them
  with rm+wx.
- tryReserveAuthFlow: steal stale locks via an atomic rename-claim to a
  per-pid path so concurrent stealers cannot both win, with a
  post-rename staleness recheck. POSIX has no compare-and-delete; the
  rename makes the claim exclusive, which is what prevents double
  helper spawns.

Tests updated for the no-delete-on-read semantics plus a claim-
contention back-off case.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
Resolves the AGENTS.md dependency-rules conflict: takes v2/main's
server-legacy devDependency rewrite (#2519/#2520) and keeps this
branch's daemon-cli additions (four externalized clients; daemon-cli in
the no-runtime-deps list).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pt corruption

Address Copilot review round 4:

- readTranscript (eval harness): a malformed record before the final
  line now throws with its line number instead of being silently
  dropped, matching the function's stated contract — only a torn final
  line (a kill artifact) is tolerated.
- connect: snapshot the SDK's default-inherited environment (PATH,
  HOME, SHELL, ...) from the calling shell and merge it under any
  configured env before the config crosses to the daemon. The transport
  otherwise evaluates getDefaultEnvironment() inside the persistent
  daemon, handing servers the environment of whichever shell first
  spawned it. Extracted the cwd/command/env pinning into an exported
  pinStdioConfigToCaller, which now also treats a type-less config as
  stdio (stdio is the implicit default).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
…ng, spec doc

Address Copilot review round 5:

- form-schema/form-prompt: minLength/maxLength are measured in Unicode
  code points per JSON Schema, not UTF-16 units — a valid astral-char
  default (or answer) was rejected / trapped in the re-prompt loop.
  Shared codePointLength() applied at all four bound checks.
- test-server-fixtures: submit_ticket ids now hash the full submission
  (djb2) instead of summary/email lengths, which collided whenever only
  contact_name changed; comment made honest about the 4-digit space.
- specification/v2_cli_tui_launcher.md: 'Shared core consumption' and
  the summary now count daemon-cli among the core consumers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
…marker cleanup

Address Copilot review round 6:

- resources/subscribe: the resourceUpdated listener now attaches before
  the subscribe handshake and buffers matching updates until the
  consumer's start() — a server notifying immediately after (or with)
  its subscribe response no longer loses that update in the window
  before the stream starts. The subscribe-failure path detaches the
  listener; ipc-glue already guarantees every stream outcome is started
  and stopped, so no leak on vanished callers.
- auth helper exit: marker cleanup now verifies the on-disk marker's
  pid is this helper's before deleting (removeOwnPendingAuthMarker) —
  past the 15-minute TTL a replacement flow's fresh marker at the same
  pathname would otherwise be deleted out from under its callers. The
  residual read-to-rm window is documented; losing it costs one extra
  sign-in prompt, never a wrong URL.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
…uto-decline

The README still said --format json auto-declines forms and every other
non-TTY caller gets a real prompt. Actual behavior since the parking
change: non-interactive callers (--format json or no TTY) get the
elicitation parked, the RPC returns an elicitationPending payload, and
the answer comes via elicitation/respond (auto-cancel after 10 minutes).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
…t/use annotate progress

A non-TTY connect hands OAuth to the detached helper and registers a
pending-auth entry, but connections/show never completed it: pollers
following connect's own "check with connections/show" guidance saw
"Sign-in: pending" forever (with the Auth line contradictorily flipped
to authorized), until some real op revived the entry.

- connections/show: when the entry is pendingAuth and the disk tokens
  are usable, run the same revive the first op would — show now observes
  (and performs) the completion. Revive failure falls back to the honest
  pending snapshot; a raced disconnect surfaces as the usual
  unknown-connection error.
- connections/list / connections/use / daemon/status stay read-only (no
  dial) but annotate pending entries whose sign-in finished:
  pendingAuthSignedIn: true, live auth, and "signed in — completing on
  next use" in human output, so a poller knows the user's part is done.
- Docs: README auth blurb, SKILL.md auth section, protocol/mcp comments.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
The daemon-cli lockfile still resolved esbuild 0.27.7 via tsup, which is
affected by GHSA-g7r4-m6w7-qqqr. Add the same esbuild override the web,
cli and tui packages already carry, and refresh the lockfile.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
…, lock race

Four fixes from maintainer review round 2 on #1783:

- disconnectAll settles each teardown independently: one failed disconnect
  (e.g. a connection_not_found race) no longer abandons the remaining
  connections and their stdio children, or makes daemon shutdown reject
  before the socket server closes.
- Both daemon socket clients now setEncoding("utf8") so a multi-byte
  UTF-8 character split across TCP chunks is reassembled by the stream's
  StringDecoder instead of being mangled into U+FFFD per chunk.
- An already-aborted signal no longer leaks a live socket: connect() was
  still called after onAbort() destroyed the socket, silently un-destroying
  it and pinning the event loop.
- acquireLock treats a pidless daemon.lock younger than a 2s grace period
  as held (a concurrent starter between its O_EXCL create and pid write)
  instead of stealing it, which could let two daemons both win and
  permanently poison daemon.token. Symmetrically, a young pidless lock
  renamed aside mid-reclaim is restored, not reclaimed.

Regression tests for all four (the UTF-8 test verified to fail without
its fix); coverage thresholds hold.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
Elicitation prompts previously created a fresh readline interface per
exchange, so answers piped up front (e.g. printf "a\nb\n" | mcpdo ...)
were buffered into the first interface and discarded when it closed —
only the first answer survived. Replace per-exchange readline usage with
a shared persistent PromptReader that queues incoming lines and hands
them to questions as they are asked, across questions and elicitation
rounds. 'Input closed' now means truly exhausted (EOF and empty queue),
so a completable form is never cancelled while queued answers remain.

Verified against the reviewer's repro: pre-fix the second piped answer
was dropped and the form cancelled; post-fix all answers are consumed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
- logging/tail: object `data` in a log notification now renders as JSON
  instead of "[object Object]" (format-human.ts).
- Sign-in helper: listen for "close" instead of "exit" so a final
  buffered stdout line (e.g. {"event":"error"}) is parsed before the
  failure path runs (auth-helper.ts).
- Failure paths now sanitize server-influenced text the same way the
  success path does: helper error messages and tool names interpolated
  into error envelopes get C0/C1 controls replaced with visible
  stand-ins (auth-helper.ts, format-connection.ts).
- Document why mcp-bin.ts and daemon/run.ts are excluded from coverage
  (vitest.config.ts).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
…ails

- Eval matcher: parse the `--flag=value` spelling; `--connection=x`
  no longer reads as an unknown boolean flag that drops the value
  (false hits/misses in eval scores).
- Eval shim: exit via process.exitCode instead of process.exit(), which
  discarded queued stdout writes and truncated large JSON results.
- skills:eval env allowlist: pass AWS_*/GOOGLE_*/CLOUD_ML_* through for
  claude — Bedrock/Vertex runs need them to authenticate.
- verify:skills: also validate shipped skills under `skills/`
  (frontmatter/structure only; no eval-case or listing-budget rules) so
  a truncated skills/mcpdo/SKILL.md cannot ship silently.
- skills/mcpdo/SKILL.md: clarify that a parked elicitation means the
  command has already exited (exit 0) while the MCP tool call waits
  daemon-side — agents must not wait on or time-box the command.
- daemon-cli README: note private mode separates daemons from each
  other, not from same-UID processes that learn the daemon dir.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
…s out stopping daemons

Two review findings from cliffhall's round 2 on the daemon lifecycle:

Park teardown owns the unwire (F6): a parked call's expiry or cancel
closed the elicitation channel without removing the call's subscriber,
while the server-side call kept running. Its stale subscriber stayed
first in line and could swallow a later call's elicitation. ParkedCall
now carries the unwire handle and every teardown path (respond, expiry,
cancel, cancelForConnection) detaches the subscriber immediately.

ensureDaemon vs. shutting-down daemon (F9): ping now reports a
stopping flag (and daemon status surfaces it, with a "(shutting down)"
marker in human output). When ensureDaemon reaches a stopping daemon it
waits for the old process to exit (pid-based, since the socket closes
before the lock is released) and then spawns a fresh one, instead of
surfacing a daemon_stopping failure to the user. Ping itself always
succeeds; observing a shutdown never restarts the daemon.

Adds regression tests for both: stale-subscriber swallow, registry
unwire on expiry/cancelAll, stopping ping/status, waitForDaemonExit
(dead pid, live-pid timeout, socket fallback), and a full ensureDaemon
wait-then-respawn integration.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
Eval harness containment (F14, review round 2): a rejecting behavior
sample used to reject the whole pool and exit the process while sibling
samples' finally blocks (daemon stop, sandbox removal) were still
pending — detached eval daemons genuinely leak that way. pool() now
takes an optional onError mapper: the behavior section records an
errored sample as a scored miss (failures note, zero calls) and keeps
going. Without onError the pool stops taking new items, lets in-flight
work finish its cleanup, then rethrows the first error. A throw from
makeBehaviorEnv now also reclaims the sample's sandbox dir. Pool
semantics pinned by unit tests.

New smoke: `npm run smoke:mcpdo` (wired into `npm run smoke`, G1)
drives the built daemon CLI end to end in a hermetic sandbox — catalog
connect, tools/call, elicitation park + respond round-trip, daemon
status, disconnect, daemon stop with verified process exit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
Step 3 of the release skill said only "generate the notes and publish".
It is now three parts:

- 3a, draft the release notes: What's Changed from the generate-notes
  API, the smoke-ledger line, any known issue, and a "Thanks for helping
  us improve" section crediting the community members whose issues the
  release addresses (maintainers and bots excluded by permission). The
  recipe reproduces 2.9.0's published Thanks section exactly. @-mentions
  feed the release's Contributors strip.
- 3b, tag and publish: the existing UI steps, plus gh release create
  with the notes file. Editing published notes is safe, since every tag's
  main.yml fires only on release: [published].
- 3c, if the release run fails: a release runs the workflow from the
  tag's commit, so fix it on v2/main, merge to main, delete the Release
  AND its tag, and re-cut. This is what 2.9.0 needed (#2551).

The AGENTS.md skills-index row for release is updated to match.

Closes #2554

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
- PREV is now the highest strict x.y.z tag below VERSION. The old glob
  also matched 2.0.0-rc.N, x.y.z-hotfix and x.y.z-amended tags, so
  cutting a stable release after an RC would pick the RC and drop
  changes (for VERSION=2.0.0 it picked 2.0.0-rc.3; now 1.0.2).
- thanks.md is truncated before the optional append, so a rerun with no
  eligible reporters never keeps a stale section.
- A re-cut re-runs the whole 3a recipe, since a fix PR can close a
  community-reported issue and change the Thanks section too.
- The re-cut no longer waives verification: gate and smoke a fix
  wherever it can be run, and only a release-only path takes the re-cut
  run as its first evidence.

Refs #2554

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
…older (Copilot)

- The body-reference extraction did scan(...)[] | .[0], which with gh's
  jq indexes the first CHARACTER of each captured string: "Closes #2554"
  became issue 2. It is now scan(...) | .[0]. Verified with gh --jq:
  "Closes #2554 … Fixes #12 … resolves #999" gave [2,1,9] before and
  [2554,12,999] after. The 2.9.0 check missed it because every PR there
  also carried a manual closing link.
- <assembled-notes.md> inside a shell block is input redirection, so
  --notes-file lost its argument. It is now a NOTES variable.

Cross-checked: the fixed recipe reproduces the published Thanks lists
of 2.9.0, 2.8.0, 2.5.0 and 2.4.0 exactly (26 reporters; three of those
lists were built independently).

Refs #2554

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
The re-cut deleted only the remote tag. The 3b manual path creates a
local tag, which would stay pointed at the broken commit: the re-tag
aborts, and a later git fetch --tags refuses to clobber it. Delete it
locally as well.

Refs #2554

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
… failures (Copilot)

- Never infer "unpublished" from dist-tags. During the 2.9.0 re-cut,
  2.9.0 existed (Validating) while dist-tags still said latest 2.8.0.
  Wait out validation and treat only an exact-version 404 as
  unpublished.
- When npm published but a downstream job (such as GHCR) failed, re-run
  only the failed job for a transient fault, and fix forward only a
  defect that cannot pass on retry. Never re-cut a version npm owns.

Refs #2554

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
…se-notes-step

docs(release): add the release-notes step and the re-cut procedure
cliffhall and others added 3 commits October 7, 2026 16:22
…acting display boundary (#2638)

#2490 made the TUI show caught error text only through errorText's
errorMessage(), which redacts URL query secrets. The Tasks tab,
Subscriptions tab and Roots editor added by #2432 still rendered
err.message directly (TasksTab through its own unredacted copy), so a
server error quoting an OAuth URL reached the screen verbatim.

All three now use the shared errorMessage(); TasksTab's copy is gone.
Each suite gains a test that throws an error quoting ?code=s3cret and
asserts the secret never reaches the display; all three fail with the
source change reverted.

Found by Copilot on the v2.10.0 milestone merge (#2637).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
run.ts wrote its startup diagnostic and called process.exit(1) at once.
On a pipe or file stderr is asynchronous, so exit could discard the
message. Await the write via core/cli's awaitableError, the same
flush-then-exit the shared CLI error handler uses.

Found by Copilot on the v2.10.0 milestone merge (#2637).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
- #78: zshDescribeEntry escaped ':' but not '\' in a _describe name.
  Escape backslashes first, then colons; a test renders a name holding
  both. Flag names never contain either today, so this was unreachable.
- #80, #81: the mcpdo stored-auth test matched its server with
  url.includes("example.com"); compare the full URL instead.
- #79: the namespace-ledger test's sentinel edit is an anchored
  /^\{/ replace, with an assertion that the sentinel really differs,
  so the 'no rewrite' check can never pass vacuously.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
…review-findings

fix: v2.10.0 merge review findings — TUI error redaction, mcpdo daemon flush, CodeQL #78-#81
Signed-off-by: cliffhall <cliff@futurescale.com>
Copilot AI balanced review requested due to automatic review settings October 7, 2026 21:05

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The new mcpdo runtime environment variables and OAuth-state reader are missing from the public environment-variable documentation.

1 open finding
1 resolved since last review

🧠 Review effort: Balanced

cliffhall and others added 2 commits October 7, 2026 17:24
…required-ruleset

ci: make DCO a required status check on v2/main (#2621)
…2546)

dependabot-alerts.mjs (twice) and sdk-watch.mjs escaped | in Markdown
table cells but not \, so a value ending in a backslash re-exposed the
pipe and broke the row (CodeQL js/incomplete-sanitization #74-#76).

One shared helper, scripts/lib/markdown-cell.mjs, escapes backslashes
first, then pipes; both scripts import it. Its test covers a trailing
backslash and checks every pipe in the output sits behind an odd run of
backslashes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
cliffhall and others added 2 commits October 7, 2026 17:32
…2640)

docs/environment-variables.md claims every runtime variable but never
mentioned mcpdo, which ships for the first time in 2.10.0.

- A new 'mcpdo connection daemon' section documents
  MCP_INSPECTOR_DAEMON_DIR, MCP_INSPECTOR_DAEMON_TOKEN and
  MCP_ALLOW_DEFAULT_CONNECTION from daemon/paths.ts, daemon/auth.ts and
  connection/dispatch.ts; they were only in the spec.
- mcpdo is named in the Read-by legend and in the 16 rows it reads
  through core/: each name was checked against the built mcpdo bundle
  (the proxy rows through EnvHttpProxyAgent in core/mcp/node/proxyFetch).

Raised by Copilot on the v2.10.0 milestone merge (#2637).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
cliffhall and others added 4 commits October 7, 2026 17:49
…lags (#2640)

Copilot on #2641, verified in source:
- mcpdo does not honour HTTPS_PROXY/HTTP_PROXY/NO_PROXY. Its two
  InspectorClient environments (connection/authorize.ts,
  daemon/connections.ts) omit fetch, so InspectorClient wraps global
  fetch (inspectorClient.ts:846) and the transport never reaches
  createProxyFetch(). The variable names are in the mcpdo bundle, but
  that code is never called on these paths.
- mcpdo has no --callback-url or --client-config flag; say the flag
  overrides apply to the CLI and TUI only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
…llback (#2640)

Copilot round 2 on #2641, verified in source:
- MCP_AUTO_OPEN_ENABLED: with no TTY and no --stored-auth-only, mcpdo
  connect returns a pending connection and authUrl instead of the CLI's
  auth-required error; true keeps the blocking flow
  (connection/mcp.ts).
- USERPROFILE: the daemon directory falls back to os.homedir(), not the
  working directory, when neither HOME nor USERPROFILE is set
  (daemon/paths.ts).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
…cell-escaping

fix: sweep table-cell escaping (CodeQL #74-#76) and document mcpdo's environment variables
Signed-off-by: cliffhall <cliff@futurescale.com>
Copilot AI balanced review requested due to automatic review settings October 7, 2026 22:42

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

OAuth reservation recovery has a double-spawn race, and packaged-skill validation lacks direct regression coverage.

2 open findings

🧠 Review effort: Balanced

Comment thread scripts/verify-skills.mjs
// truncated SKILL.md would otherwise ship silently — but no eval-case or
// listing-budget requirements: they are not part of this repo's own
// agent skill listing.
if (!override && existsSync(path.join(ROOT, "skills"))) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A real gap in repo tooling, but nothing in the published package depends on it, so it does not block the release. Filed as #2644 for v2.11.0.

@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 3: both new items are filed for v2.11.0 instead of growing the release merge.

@cliffhall

Copy link
Copy Markdown
Member Author

Copilot review loop closed after round 3. Its two new items are filed for v2.11.0 (#2643 and #2644, with the reasons above), and the one finding it still lists as open was already fixed by #2639. Final tree 87356bc1 == origin/v2/main. CodeQL is green with 0 open alerts. build and coverage pass. Locally, local:gate, Storybook and pack:verify pass, and the Firefox smokes pass in the Linux container (#2625). The ledger is updated: https://claude.ai/artifact/KQe73j1A6QcGvnpAXfx2Hb

@cliffhall
cliffhall merged commit 630eb8f into main Oct 7, 2026
9 checks passed
@cliffhall
cliffhall deleted the v2/chore/milestone-merge-v2.10.0 branch October 7, 2026 23:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2 Issues and PRs for v2

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Release 2.10.0 step 2: merge v2/main into main and cut the release

4 participants