Skip to content

docs(skills): the automation skill's OAuth recipe names a stdio MCP server's credential route as transport.credentialEnv - #30437

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-29625-stdio-credential-env
Nov 5, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/issue-29625-stdio-credential-env

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #29625

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:skills/objectstack-automation/references/integration-recipes.md 配方 6 的括注 —— stdio MCP 服务器的凭据不再说「走 transport.env」,改为「静态 auth: { type: 'bearer', credentialRef },放进 transport.credentialEnv 命名的变量,不在 transport.env 里写字面量」;开头段删去两句在 SKILL.md 与配方 4 已有家的复述,用来付这份文件的 token 天花板(10376 → 10363 字节,2591/2595 tokens,行数 183 → 183)。
  • 为什么改:PR feat(connector-mcp): a stdio MCP server takes its credential from the secrets layer through transport.credentialEnv #29621 落地后,mcp provider 把解析后的静态凭据放进子进程环境变量(packages/connectors/connector-mcp/src/mcp-provider.ts 的 credentialEnvOf);旧文字教的正是 connector-credential-literal 告警所反对的「凭据字面量写进元数据」路线,而这份文字经 npx skills add 原样装进客户项目。
  • 风险与回滚:纯教学文本,无代码、无 schema、无 eval 变化;回滚即 revert 本 commit。唯一风险在措辞:读者若把「静态 auth」读成 oauth2 也能上 stdio —— 括注开头仍写明 grant 类 http only,与 provider 的拒绝一致。
  • 先看:① integration-recipes.md 第 155–159 行(配方 6 新括注,风险 hunk);② 同文件第 9–10 行(删句);③ .changeset/29625-stdio-credential-env.md。
  • 两分钟验证:node scripts/check-skills-token-ratchet.mjs 预期绿;grep -n "transport.env" skills/objectstack-automation/references/integration-recipes.md 预期只命中一处,即新括注里的「never a literal in transport.env」。
  • 需要你做的:skills/** 是 Tier H —— GOVERNED_APPROVERS 账号 APPROVE 一次,席位随后落地。

What changes

Recipe 6 of the automation skill's integration recipes said: "mcp (http only; a stdio server's credentials ride transport.env)". That parenthetical teaches the literal-in-metadata route. It now says what the mcp provider does on main:

mcp (http only; a stdio server takes a static auth: { type: 'bearer', credentialRef } in the variable transport.credentialEnv names, never a literal in transport.env)

Read off the code, not the card (packages/connectors/connector-mcp/src/mcp-provider.ts, credentialEnvOf and normalizeTransport):

  • A stdio transport's credentialEnv names the environment variable the child is started with; the value is the resolved static credential — a bearer token, an api-key key, a basic password. The same spelling as content/docs/automation/connectors.mdx § A stdio server's credential and the connector-credential-literal advisory's hint.
  • "http only" stays true for the recipe's grant-bearing kinds: a grant-bearing auth on a stdio transport fails the boot, naming this static route.
  • A literal in transport.env is passed to the child as written, stored in metadata, and reported by the advisory — hence "never a literal".

Paid inside the file's shrink-only token ceiling (2595) by deleting one sentence of the opening paragraph that restated two facts with surviving homes: "A spec is not a secret and ships in the package" (SKILL.md § Integrations are declared, not coded: "a packaged spec"; recipe 1's spec: './src/connectors/stripe.trimmed.json') and "an api flow's hook secret goes to the deployment and to the sender" (SKILL.md's secretRef row: "an env var the deployment fills"; recipe 4: "The sender signs each post under CARRIER_HOOK_SECRET"). No re-wrap bought a line: skills/** is 6 added / 6 removed, net 0 lines, net −13 bytes.

Also checked, unchanged: recipe 1 never said the old route; recipe 2's // stdio needs the host's allowlist comment still holds; neither SKILL.md, evals/integrations-declared.json nor evals/flows-triggers-approvals.json mentions stdio, transport.env or credentialEnv, so no eval asserted the old route and none changes.

Changeset: .changeset/29625-stdio-credential-env.md, '@objectstack/skills': patch, carrying the two declaration lines.

Tests

Prose only; no test file changes. The gates that read this surface are under Verification.

Acceptance notes

  • packages/spec declares no Zod schema for providerConfig.transport.credentialEnv; the key is judged at boot by the mcp provider and by the connector-credential-literal advisory, which is where connectors.mdx points too. Observed only, consistent with the transport being provider-owned; noted, not filed.
  • skills/** line budget: net 0 (budget ≤ +3). Token ceiling: 2591 of 2595 after the edit.

Verification

Head ef7770a8; git status --porcelain empty when the union ran, so every reading below is of this head's tree.

node scripts/pm/dispatch-gates.mjs --tier --repo objectstack-ai/objectstack skills/objectstack-automation/references/integration-recipes.md → Model tier — MANDATORY: claude-fable-5-1, clause ① (skills/**, no one-line exit). This run is that tier.

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; the tool took the change set from the merge-base) derived 43 families. Each ran in the foreground with its exit captured before any pipe, then --ran reconciled:

`--ran` verdict: 43 derived, 38 run, 5 NOT-MEASURED, 0 UNRUN
Run reconciliation — 43 derived, 38 run, 5 NOT-MEASURED, 0 UNRUN.
  NOT-MEASURED · DERIVED (5) — your record carries exit 3 for these, the number a gate refusing its own prerequisite exits with.
    - pnpm --filter @objectstack/lint run check:doc-formula-expressions   [recorded exit 3 on line 19 — PREREQUISITE NOT MET]
    - pnpm check:dts-closure   [recorded exit 3 on line 29 — PREREQUISITE NOT MET]
    - pnpm check:dual-build-cjs-loads   [recorded exit 3 on line 30 — PREREQUISITE NOT MET]
    - pnpm check:lean-entry-closure   [recorded exit 3 on line 32 — PREREQUISITE NOT MET]
    - pnpm check:sourcemap-no-sources-content   [recorded exit 3 on line 42 — PREREQUISITE NOT MET]
✓ dispatch-gates --ran: 43 derived famil(ies) accounted for — 38 run, 5 NOT-MEASURED (5 DERIVED from a recorded exit 3).

The five read a built dist/ across the whole workspace ("Not one of the 131 workspace package(s) under . has a dist/"); this diff touches no package, so no build closure is owed locally and CI's Lint & Repo Gates carries them. Every other family exited 0.

The three gates the dispatch named, verdict lines
$ node scripts/check-skills-token-ratchet.mjs            # exit 0, re-run on ef7770a8
✓ check-skills-token-ratchet: 57 authored bundle file(s) within their ceilings; 10 generator-owned file(s) and 29 eval fixture(s) measured, not ratcheted.

$ pnpm --filter @objectstack/spec run check:skill-refs   # exit 0
✅ 9 generated files in sync with packages/spec

$ pnpm check:doc-authoring                               # exit 0
✓ doc authoring guard: 71 published skill files clean — no internal issue-id references.

Dogfood battery: n/a — no ! in the title and no (narrowing) arm.


Generated by Claude Code

…erver's credential route as transport.credentialEnv

Recipe 6 of objectstack-automation/references/integration-recipes.md said a
stdio server's credentials ride transport.env, the literal-in-metadata route
the connector-credential-literal advisory warns against. It now says what the
mcp provider does on main: the grant-bearing kinds stay http only; a stdio
server takes a static auth with a credentialRef, handed to the child process
in the variable transport.credentialEnv names, never a literal in
transport.env. Paid inside the file's token ceiling by dropping the opening
paragraph's restatement of the packaged spec and of where an api hook's secret
goes (SKILL.md and recipe 4 keep both).

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012MmDsR4AZoJZyzKQcBEESh
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#2 · os-lizheng · session_012MmDsR4AZoJZyzKQcBEESh

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ef7770a855155721cfbb2601b7327c0e58e19a54
Local-runs: none
Scope: skills/objectstack-automation/references/integration-recipes.md + packages/connectors/connector-mcp/src/mcp-provider.ts, content/docs/automation/connectors.mdx, packages/lint/src/lint-connector-credential-literals.ts, packages/lint/src/rule-explanations.ts, skills/objectstack-automation/SKILL.md, skills/objectstack-automation/evals/integrations-declared.json, scripts/check-skills-token-ratchet.mjs, packages/skills/scripts/sync-catalog.mjs

Rendered 2026-11-05T11:37Z against card #29625 (body and all three comments), PR #30437 (body, file list, net diff against main) and the check-runs on the head. Read-only: main read at f7afab86 through git show origin/main:…, the head's files through the contents API. The PR base 1013ef11 is an ancestor of that main, the recipes blob on main (c7b9098c5ae) is the diff's pre-image, and none of the cited files moved between the base and main, so every judgment below holds on the live tree. The rest of the diff (.changeset/29625-stdio-credential-env.md) is the lane seat's ACCEPT at TIER_DEFAULT; it is read here for ② only.

① Derived judgments

  • Accept-set: no change — right. No schema, gate or code moves. packages/spec declares nothing under providerConfig.transport (provider-owned), and no generated artifact mirrors the recipes file (packages/spec names integration-recipes nowhere; references/_index.md is generated from spec exports, not from this file), so nothing was owed to any gen:*.
  • Public surface: the published catalog changes in two places — right, both. skills/** ships byte for byte as dist/skills/ of @objectstack/skills (sync-catalog.mjs), so recipe 6's parenthetical (a route taught, the old one removed) and the opening paragraph's lost sentence are customer-visible teaching changes. Both judged in ④.
  • The parenthetical's three claims, each read in the provider on main — right. First, a grant-bearing auth on a stdio transport fails the boot: createMcpProviderFactory throws when ctx.credentialHandle is set and the transport is not http, and its message spells the same auth: { type: 'bearer', credentialRef } plus providerConfig.transport.credentialEnv route. Second, a static credential reaches the stdio child in the variable credentialEnv names: credentialEnvOf places secretOf(auth) (bearer token, api-key key, basic password) into the spawn env. Third, a literal in transport.env goes to the child as written (normalizeTransport keeps env whole) and is the fourth map the connector-credential-literal advisory judges. "http only" for the grant-bearing kinds is exact.
  • Token ceiling — right. Head file 10363 bytes, ceil(10363/4) = 2591, at most the ratchet row's 2595; the row is untouched. Paid by deleting content (one sentence), the only currency the lane allows; the paragraph re-flow is the consequence of that deletion, not a re-wrap buying lines. Net 0 lines (+2 in recipe 6, −2 in the opening paragraph).
  • Evals — right. On main, nothing under skills/objectstack-automation/** other than the edited line names stdio, transport.env or credentialEnv, and no eval asserts the deleted sentence's words (eval 2 already says "packaged OpenAPI 3.x document"). No eval change was owed.
  • Tier — right, with its consequence. skills/** is Tier H in Prime Directive 14 and the register; this record is the contract-tier review of record and lifts nothing by itself — an authorized APPROVED review does, as the PR body's "需要你做的" says. The draft flag stays until then.
  • Gate verdicts on the head, read at 2026-11-05T11:37Z: 41 check-runs, all completed; 28 success, 13 skipped, none failed or pending. The required set: Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Governed Surface Queue Guard, Test UI, Live E2E UI all success; Lint UI, Type Check UI, Build & E2E UI skipped by the path filter (no ui/ change). Check Changeset, Check PR Size and the PR-body guards success.
  • Observed, not a tree finding: the PR body carries the file reading (bytes, tokens, lines, before and after) but not the whole-package SKILL.md-sum reading the lane's two-readings rule asks for; no SKILL.md is touched, so that reading is trivially unchanged. For the lane seat's body pass.

② Semver level

.changeset/29625-stdio-credential-env.md declares '@objectstack/skills': patch, with Clause-②: no and Metadata-protocol: none on both the changeset and the PR body. Right on every count: @objectstack/skills (packages/skills, public, in the fixed group) publishes skills/**, so the diff publishes and skip-changeset would be wrong; the change is prose, with no key, value, requirement or meaning a metadata author can write changed, so the kind is none and the level patch; Clause-②: no is the right declaration for a diff that neither widens nor narrows the spec. Check Changeset on the head is success.

③ Boundary flags

Dev deviations, five, each answered:

  1. The 43-family union ran on the working tree immediately before the commit, same bytes, the ratchet family re-run on the head — accepted; the check-runs on the head are the gate verdicts in any case.
  2. Five families NOT MEASURED locally (whole-workspace dist/ prerequisite, exit 3) — accepted; the diff touches no package, and Lint & Repo Gates on the head is success.
  3. Model-free trailer pair — accepted; that is the form this repository requires.
  4. No labels on pr_create — accepted; Auto Label applied documentation, size/s, tooling.
  5. Card and PR reads through gh api GET, no MCP tool — accepted.

open_questions: empty; nothing to answer.

Out-of-scope observation (carrier none): "packages/spec declares no Zod schema for providerConfig.transport.credentialEnv" — verified: git grep credentialEnv origin/main -- 'packages/spec/**' is empty, and the spec's connector redactor names only the four header/env maps. Noted-not-filed is the right disposition: the key is provider-owned under ADR-0024, a malformed value is refused at boot by name, and the shape an author would fall into — a static auth on stdio with the key missing or misspelled — is the advisory's own "auth reaches nothing" finding at os validate. Not escalated.

The card's serial constraint (after PR #29621) is met: the provider code the text now describes is on main. The dispatch's "stop on breach" clause was not reached.

④ Design judgment

skills/objectstack-automation/references/integration-recipes.md, judged against the mcp provider on main, connectors.mdx § A stdio server's credential, the connector-credential-literal advisory (its hints and its os explain text), the card's Wanted, and the skill's three integration evals.

Recipe 6 parenthetical (lines 155–159) — holds.

  • Route: matches the provider claim for claim (① above). The docs page teaches the identical shape with type: 'bearer' in its own example; the advisory's env hint, its os explain paragraph and the provider's refusal text all spell auth: { type: 'bearer', credentialRef } plus transport.credentialEnv. A customer agent that gets this wrong meets exactly the sentence the recipe now teaches.
  • type: 'bearer' alone, where the provider also places api-key and basic — a fair example, not a misleading narrowing. The card asked for this spelling. The file's Holds on every recipe auth bullet (lines 24–30) already enumerates every static variant "each with a credentialRef", so the alternatives are taught in the same file. And on a stdio child the kind is semantically inert: headerName and paramName are not read, the child receives one variable either way, so an agent that writes bearer for a key-shaped secret still gets a correctly routed, working instance; the one kind with distinct stdio behaviour (basic, username to transport.env) is covered by the docs page the file links. Widening the parenthetical would cost more than the four tokens of headroom and buy no correctness.
  • Tried to break it. "takes a static auth … in the variable transport.credentialEnv names" compresses "the resolved secret is handed in the variable"; an agent that wrote the auth object into credentialEnv is refused at boot by name (ENV_NAME_RE), and the word "names" keeps the key a name. "never a literal in transport.env" is a teaching rule, not a refusal: the provider accepts the literal and the advisory warns, and a basic username in transport.env is not a credential literal, so the rule does not over-reach. The half-declared shape (static auth, no credentialEnv) is not taught here; it is the advisory's third finding at authoring, and the sentence couples the two halves, so an agent following it writes both. The parenthetical sits in the OAuth recipe beside line 183's "never park a short-lived token behind a bearer ref", so the stdio static route cannot be read as "put the OAuth token behind bearer". Recipe 2's "stdio needs the host's allowlist" still holds (declarativeStdio default deny). The inline code span wraps across a line; CommonMark folds the newline into a space, so it renders as one span and reads whole as raw text.
  • No finding.

Opening paragraph (lines 6–10): the deleted sentence's two facts survive in what npx skills add installs — holds.

  • "A spec is not a secret and ships in the package": SKILL.md § Integrations are declared, not coded reads "provider: 'openapi' | 'mcp' | 'rest', a packaged spec, auth.credentialRef (the customer's administrator supplies the value)" — the packaging and the contrast with the one supplied value in one clause; the kept opening sentence still names "the one thing the customer's administrator supplies"; recipes 1 and 5 show the spec as a package file and as an inline document; eval 2's expected output says "packaged OpenAPI 3.x document". Only the literal words "not a secret" go, and nothing in the file offers an agent a way to treat a spec as one.
  • "an api flow's hook secret goes to the deployment and to the sender": SKILL.md's secretRef row — "an env var the deployment fills, not OS_*; unfilled ⇒ 503. Never a literal" — is the deployment half; recipe 4 — "The sender signs each post under CARRIER_HOOK_SECRET (SKILL.md Signature bullet)" — is the sender half, with the HMAC form in that bullet. Both halves ship in the same bundle the catalog copies whole.
  • No eval asserts either sentence; the paragraph reads cleanly after the cut.
  • No finding.

Must-fix: none. Should-fix: none.

Implemented-by: claude/issue-29625-stdio-credential-env
Reviewed-by: session_012MmDsR4AZoJZyzKQcBEESh

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#2 · os-lizheng · session_012MmDsR4AZoJZyzKQcBEESh

席位核对 — ACCEPT · domain:skills#2 · 2026-11-05T11:38Z

Head-sha: ef7770a855155721cfbb2601b7327c0e58e19a54

维护者速读

  • 改了什么:objectstack-automation 技能配方 6 的一处括注 —— stdio MCP 服务器的凭据不再教「写进 transport.env」,改教静态 auth: { type: 'bearer', credentialRef } + transport.credentialEnv 指名的环境变量;开头段删一句复述(两件事实在 SKILL.md 与配方 4 仍有家),用来付 token 天花板。行数净 0。
  • 为什么改:旧文字教的是「凭据字面量写进元数据」,正是 connector-credential-literal 告警反对的路线;这段文字经 npx skills add 原样装进客户项目。
  • 风险与回滚:纯教学文本,无代码/schema/eval 变化;回滚即 revert。
  • 先看:integration-recipes.md 配方 6 括注(第 155–159 行)。
  • 两分钟验证:grep -n "transport.env" skills/objectstack-automation/references/integration-recipes.md 只剩「never a literal in transport.env」一处。
  • 需要你做的:skills/** 是 Tier H。契约复核 PASS 在案(评论 6657015078,Head-sha: ef7770a8…);请 GOVERNED_APPROVERS 账号 APPROVE,席位随后落地。

席位意见:可合。改动小且对得上 main 上 provider 的实际行为;只举 bearer 一例是公平示例,其余静态类型同文件「Holds on every recipe」已列。

核对

  1. 形态:draft → main;正文首行 Fixes #29625;两条机器行 Clause-②: no · Metadata-protocol: none 在正文与 changeset 中。文件面 = 认领申报面(integration-recipes.md + .changeset/29625-stdio-credential-env.md)。
  2. 前提实测:origin/main 上 packages/connectors/connector-mcp/src/mcp-provider.ts 的 credentialEnvOf / normalizeTransport 由席位亲读,三条断言成立;前序 PR feat(connector-mcp): a stdio MCP server takes its credential from the secrets layer through transport.credentialEnv #29621 已合。
  3. 门禁 check-runs(head ef7770a8,2026-11-05T11:38Z):41 个,28 success、13 skipped、0 失败/挂起;mergeable_state: clean。
  4. 接受的偏差:5 个需全仓 dist/ 的门禁族本地 NOT MEASURED(exit 3),由 CI 覆盖且已绿;commit trailer 取 AGENTS.md 无模型名形式。
  5. 范围外去向:一条观察(packages/spec 无 transport.credentialEnv 的 Zod 声明,transport 归 provider 所有)只进 Acceptance notes,不属立卡三类 → Acceptance notes。Skills-docs: 不欠(种类 none)。
  6. 落地条款:Tier H(check-governed-merges --pr 30437:skills/** ×1,22 行 ≤ 3000)。获授权 APPROVED 后席位过落地前检、清标、pr_ready + automerge_enable。changeset 正文逐句已核:标题行、两条机器行、「Recipe 6 … said … transport.env」「It now says what the mcp provider does …」「Paid inside the file's token ceiling …」「No eval asserted the old route; none changes.」—— 四句与 diff 一致。

Generated by Claude Code

@objectstack-fleet objectstack-fleet Bot added the needs-user-decision Blocked on a maintainer decision — do not dispatch; decision cards only, never on a PR label Nov 5, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review November 5, 2026 11:40
@os-zhuang
os-zhuang added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit 3abe011 Nov 5, 2026
47 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-29625-stdio-credential-env branch November 5, 2026 11:57
@objectstack-fleet objectstack-fleet Bot removed the needs-user-decision Blocked on a maintainer decision — do not dispatch; decision cards only, never on a PR label Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

2 participants