You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Milestone merge of v2/main into main for the v2.9.0 release — 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
87bb185e chore: merge v2/main into main for the v2.9.0 release
36f3d819 chore: merge v2/main into main for the v2.9.0 release
e78127a5 chore: merge v2/main into main for the v2.9.0 release
# three merges: two smoke findings (#2534, #2535) and a CodeQL finding (#2541), each fixed on v2/main and re-merged
$ git rev-parse origin/v2/main^{tree}
fa73c5946a6cc4f3f92cb2c701c57791e807bc32
$ git rev-parse HEAD^{tree}
fa73c5946a6cc4f3f92cb2c701c57791e807bc32
The merged tree is byte-identical to origin/v2/main. Version on the tree: 2.9.0 (bumped on v2/main in #2528 / PR #2529).
Every process (web backend, daemon, CLI) that persists OAuth state used to
flush its whole in-memory snapshot over the shared oauth.json — so a writer
holding a stale snapshot erased entries other processes wrote after it last
read the file (observed live: a background EMA flow wiping a fresh login).
Every write already enters through a mutation scoped to named entries, so
persistence now names what changed and merges only that:
- oauth-persist.ts: OAuthPersistSections + pure mergeOAuthSections (named
servers/idpSessions keys overlaid onto a fresh read; absent = deletion),
parseOAuthPersistSections for the wire form; backends accept an optional
sections arg; the remote backend forwards it as a ?sections= query param.
- oauth-storage.ts: persist(sections) snapshots inside the queued closure
(fresh at write time); every mutation passes its sections, with the
enterprise-managed sweep capturing its URLs before clearing them.
- oauth-persist-file.ts: shared writeOAuthSections = cross-process file
lock -> fresh read -> merge -> atomic write; lock failures rethrown with
OAuth wording and the original as cause.
- remote server storage route: sectioned POSTs apply the same shared locked
merge (400 on bad descriptors or non-OAuth bodies); plain POSTs and the
client store are unchanged.
- cli.ts refreshStoredAuthToken: its hand-rolled read-modify-write now
persists through writeOAuthSections — same lock, same merge, merged
against the file at write time.
Memory is deliberately not refreshed from the merged result: overwriting it
could revert concurrent in-process mutations, and reads staying cached is
fine — correctness comes from the per-mutation read-modify-write.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
…opening a PR (#2464)
AGENTS.md now requires the loop, unprompted, after every PR, and requires
findings to be weighed against the issue with scope expansion declined.
pr-flow step 7 becomes the loop: request, wait for a posted review or for
Copilot's pending request to lapse without one (plus a 25-minute cap), then
a stop/continue table. The first clean round is the stopping point; no
confirming round. The description leads with "create a PR for an issue" and
eval cases pin that prompt shape.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
review)
A timeout fires only while the request is still pending, and requestReviews
with union:true is a no-op for an already-requested bot, so a retry would
start nothing new.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
…#2464)
Closing keywords do not link a PR whose base is not the default branch, so
a v2 card showed no linked PR. The GraphQL mutation adds a manual closing
reference, which populates the card's Linked pull requests field. Verified
on #2466/#2464 with the manual link removed first.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Split oauth.json persistence so secret material (acquired tokens,
client secrets, IdP session tokens) is written to the OS secret store
while non-secret residue stays in the file:
- New core/auth/node/oauth-secrets.ts: pure split/join/policy module
mapping server entries to oauth:<serverUrl> fields (per-issuer and
legacy tokens/client-secret/prereg-client-secret) and IdP sessions
to oauth-idp:<issuer>.
- Rewrite oauth-persist-file.ts: writeOAuthSections splits secrets to
the store, readOAuthStore joins them back (store wins over file
plaintext) and lazily migrates plaintext secrets when the store is
durable, removeOAuthStore purges store entries. Store write failures
degrade to memory-only with a once-per-reason warning; secrets are
never written back to the file.
- New MCP_INSPECTOR_PERSIST_TOKENS=all|access|none knob controlling
which acquired tokens persist (write-side; registration client
secrets always persist). Invalid values warn and default to all.
- Remote storage routes special-case the oauth store (sectioned and
full-replace writes via locked merge+split, purge on DELETE);
sectioned writes on other stores are rejected.
- CLI reads stored auth via the joined readOAuthStore so migrated
tokens remain visible to --wait-for-auth and refresh.
- Pin MCP_INSPECTOR_SECRET_STORE=memory in web/cli test configs so
tests never touch the real OS keychain.
- Docs: environment-variables.md and secret-storage.md updated.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Bob Dickinson <bob.dickinson@gmail.com>
Step 1 moved the card to In Progress but never assigned the issue, so a card
could sit in progress with nobody on it. gh's @me resolves to the
authenticated user, so an agent assigns the maintainer it works for. The
AGENTS.md 'When work begins' rule says the same.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
#2404)
The stable ext-skills spec settles the question SEP-2640 left open:
GetSkillResult extends CacheableResult, so ttlMs and cacheScope are
REQUIRED. Add ModernGetSkillEnvelopeSchema and select it from the
negotiated era in InspectorClient.getSkillResult, mirroring skills/list.
Legacy results stay permissive. Update the "left open" comments and the
roadmap's open-gap note.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
…nders Apps (#2403)
InspectorClient advertised io.modelcontextprotocol/ui by default in every
client, so the CLI and TUI told servers they support MCP Apps although they
cannot render one. The UI registry entry now requires an App renderer: its
default applies only when the new `rendersApps` option is set (or an
app-elicitation renderer is supplied). The web client sets it; the CLI and
TUI no longer claim the extension. An explicit advertisedExtensions override
still wins, and the CLI exposes it as `--advertise-apps` for probing servers
that gate App tools on the advertisement.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
A reader that exits early (| head, | grep -m1, quitting less) made the
next stdout write fail with EPIPE, and with no 'error' listener on
stdout that was an uncaught event that crashed the CLI. The stream path
now listens on stdout for its lifetime: EPIPE unsubscribes and resolves,
any other stdout error unsubscribes and rejects into the CLI error path,
and every listener is detached on each exit, including start() throwing.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
A non-conforming server can send a form-encoded secret with an
un-escaped '&', which split the value into a masked key=prefix pair
plus '='-less tail segments that were shown verbatim in the Network
tab. Fold every '='-less segment following a masked pair into its
placeholder.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
…with a check (#2470)
Step 1 gave a command for the assignment only, and step 6 ended on a
trailing "Move the card to In Review" sentence, so agents ran the code
block and skipped the card move. Both steps now carry a block that moves
the card and ends by printing its Status; the step is done only when it
prints the target column.
Every id is resolved by name at run time (project, Status field, option)
and the card is found through issue.projectItems, so no /board-ops option
id is copied and a recreated option still resolves.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
…2405)
`--verify` gave a `resources: "dynamic"` skill the same verdict as one whose
every file hashed clean: `outcome: "verified"`, `ok: true`, exit 0 — though
nothing was hashed.
- core: a fourth `outcome`, `unverifiable` (SEP-2640's own word for it),
below `failed` and `incomplete` in precedence; `ok` stays true since
"dynamic" is a conforming wire form. New `anySkillUnverifiable` helper.
- cli: the headline no longer says "Verified" for such a skill and names how
many advertised no digests; new opt-in `--require-digests` exits 9
(`skills_unverifiable`) for a CI job standing in for a host that declines
unverifiable skills. Exit-code selection moved into one helper shared by
skills/list and skills/get. Default exit stays 0.
- tui: the Skills pane says UNVERIFIABLE instead of Verified.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Copilot's testing -> test-servers hand-off on the pagination prompt missed
because the first move was never `testing` (a grep, or an issue-triage
detour) — never a loaded `testing` that failed to follow its pointer. So
the description, not the body, was the lever: it now opens with writing a
test or end-to-end/integration coverage of an MCP operation.
Also tells a caller arriving at test-servers that every showcase-config
field (maxPageSize included) is a createTestServerHttp option, and brings
AGENTS.md's listing-cost figures up to 3,900/4,000.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
…#2471 review)
Web set rendersApps unconditionally, so with no sandbox URL — where the
Apps screen reports that MCP Apps cannot run — it still advertised the UI
extension. Gate it on the confirmed sandbox URL, as appElicitation already
is. A Server Settings override can still force the extension on.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Waiters used to race: every one polled the lock and whichever poll landed
first after a release won, so the gate queued longest could lose race
after race to newer arrivals and hit the 45-minute total budget while
they ran. Each waiter now writes an arrival-time ticket to queue/ beside
the lock, and only the head of the line asks for the lock. Dead tickets
(process gone, or unrefreshed for STALE_MS) are pruned by the next
waiter; a live waiter whose ticket was pruned restores it under the same
name and keeps its place. An unwritable queue costs only the ordering.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
…2476 review)
A waiter behind a live ticket can find the lock free while the head is
between polls; the waiting, progress and give-up lines then named a
holder that did not exist. describeWait names the holder only while the
lock is held. Also make the FIFO test wait for the clock to pass b's
arrival before starting c, since both share a pid and a same-millisecond
ticket would be ordered by its random suffix.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Both relay waits (browser RemoteClientTransport and Node RemoteSession)
are keyed by the numeric request id the SDK stamps as progressToken. A
server that echoes the token back as a string ("4") missed the Map
lookup, so progress never re-armed the flat 60s relay deadline and the
web client timed out a call a direct transport keeps alive.
Add waitForProgressToken, which tries the exact key and then falls back
to Number(token) for a string token, matching the SDK Protocol's own
Number(progressToken) correlation. Both relays use it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
…apps (#2471 review)
The Server Settings form resolved the UI extension's checkbox from the raw
registry default, so with no sandbox it showed "advertised" although the
client no longer declares it — and the modal's reconverge-to-default logic
made a true override impossible to save. Both now use the shared
isAdvertisedByDefault(ext, rendersApps), fed from App's sandbox URL.
The CLI rejected other connection-only flags ahead of its short-circuit
paths but accepted --advertise-apps there and ignored it; it is now
rejected on --list-stored-auth, --print-handoff and servers/list|show.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
open() resolves with the ChildProcess as soon as it has called spawn(),
and a spawn failure (ENOENT: the opener missing from PATH) arrives
afterwards as an 'error' event on that child. open only listens for it
with { wait: true }, which on macOS adds `open -W` and waits for the
browser to quit, so it is unusable here. Unlistened, the event crashed
the CLI before #2410's fallback line could print.
openUrl now chains a listener directly onto open's promise: a microtask,
so it attaches ahead of the process.nextTick that emits 'error' or
'spawn'. It settles on 'spawn', rejects on 'error', and stays attached so
a later error is absorbed. The TUI and web launcher share the defect and
are tracked in #2533.
Closes#2531
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
From a timer or I/O callback Node drains process.nextTick before promise
reactions, and on macOS open reaches spawn() with no earlier await, so a
listener chained only on open's promise could attach after a spawn
'error' had already been emitted (Copilot, #2534). Calling open from
Promise.resolve().then(...) puts the spawn and the listener in the same
microtask drain whatever the caller's context. The new test enters from
setImmediate and failed before this change.
Refs #2531
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
joinSensitiveContinuations appended a continuation to the last entry in
out, so an empty segment kept in between (access_token=abc&&def) became
the fold target and the tail leaked as %26def= (Copilot, #2535). Track
the sensitive pair's index instead, matching the display masker's
existing behavior for this shape.
Refs #2532
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Copilot round 1: 2 findings, both confirmed, neither a regression or a release risk, and no change made on this branch (merge-PR rule: the tree must stay identical to origin/v2/main).
redactUrlsInText stripped a URL's trailing sentence punctuation with the
unanchored regex /[.,;:!?)\]']+$/, which rescans a punctuation run from
every start position when the run does not end the match: quadratic.
The text is server-controlled (an HTTP error body lands in the error
message), so a body of http://a/? plus a long run of '!' before an 'x'
stalled the CLI's error path: 60k characters took 3s, against 0s on
2.8.0. CodeQL flagged it high on the v2.9.0 merge PR (#2536).
Replace it with a backward scan over the same character set. Behavior is
unchanged; the same probe now answers in ~180ms at 60k and at 400k.
Closes#2540
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Update report to reflect SHA-pinned dependency coverage
scripts/dependency-refresh.mjs:175
The cleared report still states that commit-SHA refs are “deliberately not covered,” but this branch now ranks those refs from their exact # vX.Y.Z comments. Consequently a successful monthly sweep publishes a false limitation precisely when SHA-pinned updates were checked. Update the generated note and its existing assertion in dependency-refresh.test.mjs to describe the new coverage.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2530
Milestone merge of
v2/mainintomainfor the v2.9.0 release — step 2 of thereleaseskill.This is a pure merge
No commits of its own, only merge commits:
The merged tree is byte-identical to
origin/v2/main. Version on the tree: 2.9.0 (bumped onv2/mainin #2528 / PR #2529).Verification
Ledger: https://claude.ai/artifact/3cWteCtohesQrCSgujccvn (maintainer review artifact; one row per closed milestone issue with what was run and what was observed)
npm run local:gate→ green (exit 0, 4m54s) on the final treenpm run pack:verify→ OK (23 files, 4.53 MB unpacked)npm audit→ 0 vulnerabilities in all five installsFound by the smoke, fixed on
v2/mainCLI crashes with an unhandled 'error' event when the browser opener cannot be spawned #2531 → PR fix(cli): catch an opener that cannot be spawned instead of crashing #2534: the CLI crashed on an opener that could not be spawned (CLI's open() call has no timeout, no catch, and no fallback message #2410's fallback never printed)
Recorded form bodies leak a secret's tail when it contains a raw '&' (core redactBody re-splits before the web masker) #2532 → PR fix(core): redact a form secret's raw-& tail in recorded bodies #2535: core
redactBodyre-split a form secret containing a raw&, leaking its tail (Form-encoded secret masking can be defeated by an unescaped & inside the secret value itself #2422's guarantee did not hold in the app)CLI error-envelope URL redaction uses a quadratic regex on server-controlled text (CodeQL, ReDoS) #2540 → PR fix(cli): split trailing punctuation off redacted URLs in linear time #2541 (found by CodeQL on this PR): CLI/TUI error output may not apply the same URL-redaction as the web client's OAuth timeout path #2423's CLI URL redaction used a quadratic regex on server-controlled error text (ReDoS; 3 s at 60k characters, now ~170 ms at 400k)
Follow-up filed for v2.10.0: #2533 (same opener defect in the TUI and web launcher).
🤖 Generated with Claude Code