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
{{ message }}
Repository navigation
service-automation: a held connector instance logs its configuration-fault ERROR with {"error":"undefined"} instead of the reason, and the mcp oauth2-over-plain-http refusal sentence ends with ".." #28939
Filed by the dispatching PM seat session for the W4-MCP runner session_01JQwxbWWuY7DoeEEbb5fxS6, whose own write was refused by its session permissions (RUNNER rule 2: staged text, posted verbatim through the same door).
立卡门类别:① 已复现产品缺陷(reach: 启动日志与 POST /api/v1/automation/connectors/NAME/connection 400 实测;拒绝句属于随版本发出的错误文字,立卡门例外照立)
A declarative provider: 'mcp' instance with auth: { type: 'oauth2', grant: 'authorization_code' } whose transport url is plain http to a non-loopback host (http://192.0.2.2:41520/api/v1/mcp) is correctly held degraded. Two defects in what it prints:
The boot ERROR line carries no reason: ERROR [Automation] failed to materialize connector instance 'qa_mcp_oauth_plain' via provider 'mcp' (ADR-0097): a configuration fault, so the instance is no longer retried. Fix its declaration; an edit or a restart re-attempts it. {"error":"undefined"}
The reason only appears on the following WARN line. An operator grepping ERROR lines for the cause reads the string "undefined".
The refusal sentence (the degradedReason, and the 400 VALIDATION_ERROR that POST /api/v1/automation/connectors/NAME/connection answers) ends "… plain http:// is accepted only to a loopback host.." — two periods.
Mechanism (read, not changed)
packages/services/service-automation/src/plugin.ts (~line 3624, the held-instance branch) calls ctx.logger.error(message, undefined, describeThrownForLog(info.cause)); the comment above says "the provider's message rides meta", but this path's info.cause is not a thrown provider error, and the logger renders the undefined error argument as {"error":"undefined"}. info.reason (which holds the sentence) is not passed.
The constant at ~line 753 of the same file already ends with "." and a caller appends another.
Reproduction
Declare in an app's connectors: a defineConnector({ name: 'qa_mcp_oauth_plain', type: 'api', provider: 'mcp', providerConfig: { transport: { kind: 'http', url: 'http://192.0.2.2:41520/api/v1/mcp' } }, auth: { type: 'oauth2', grant: 'authorization_code' } }) (with ConnectorMcpPlugin loaded, as the showcase does).
Boot with OS_BASE_URL=http://localhost:PORT; read the boot log line for that instance.
As admin, POST /api/v1/automation/connectors/qa_mcp_oauth_plain/connection {} → 400; read the message ending.
Expected: the ERROR line names the reason; the sentence ends with one period. Actual: as above.
Full evidence chain: #28937 (Part 2, connector-mcp).
Triage: retired lane label · domain:services → domain:server · 2026-11-02T04:53Z
⛔ Not a claim.
domain:services is a retired alias of domain:server, so no lane query reads it, and the card would be invisible. Relabelled; p3 and area:automation kept. The card also has no pm state, so it gets pm:queue: a named location with nothing to ask.
Claim: PM loop round 1 · card thread · 2026-11-02T05:23Z
Session: session_01FEkQ5kE5BrPUPNMMyv6oCH (the seat); the card thread is session_01FEkQ5nw17JgWHWmJM5HZNT
Account: os-project-manager (the seat's linked user; the card's assignee)
Branch: claude/project-thread-29r7m3 (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
Worktree: the card thread's own container checkout
Domain: domain:server
Seat: domain:server#3
Ruling-ref: the maintainer, 2026-11-01T03:46Z: 「server 车道现在就是你负责,任务有一大堆啊,都可以派,包括 p3」, with 2026-10-26T10:29Z 「p1 p2 处理完了,就可以处理 p3」, and 2026-11-01T06:39Z 「server 队列还是很多任务,再开一个席位吧」. Seat 1 split the server queue and gave this card to seat 3 under the maintainer's 2026-11-02T05:15Z 「队列进了很多任务,抓紧派发」.
File surface: packages/services/service-automation/src/plugin.ts (held-instance branch and the refusal constant only), a service-automation test, a changeset
Container & model: card thread cmsg_01TenzsRopZj4jMbvjX8uz1X1nKKLC3bjaEUZnprGuxxth, mode:thread, model: opus (--tier names no path mandate: PM's call, default), effort: high
Clause-②: no — log meta and one character of refusal text; seat ACCEPT at TIER_DEFAULT
Metadata-protocol: none (expected; the dev measures and declares)
Responsibility: domain:server seat 3 owns the PR to merge
Thread-read: 6569389321
Premise: reported by domain:devx#1 (QA #28937), measured on boot log and the 400; the dev re-measures first
Serial constraints cleared: none in the held-instance branch; seat 3's queued #28897 may touch arming code elsewhere in plugin.ts/engine.ts, serialized by the seat
Resume-from: none — a fresh branch from current origin/main
State: → pm:dispatched, assigned to os-project-manager, recorded by this claim. Order: pm-orders/server/order-28939.md (project folder).
os-dev-report
{
"issue": 28939,
"status": "done",
"branch": "claude/project-thread-29r7m3",
"pr": "#29000",
"session": "session_01FEkQ5nw17JgWHWmJM5HZNT",
"premise_still_valid": true,
"summary": "Both defects measured on main 135054e through the built dist: the held mcp oauth2-over-plain-http instance's boot ERROR record carried {"error":"undefined"} (failDegradedConnectorInstance logged describeThrownForLog(info.cause) with cause undefined on the grant-refusal caller), and the connection door's 400 VALIDATION_ERROR ended '..' (connectionDoorSubject appended a period to oauth2NotServableReason, whose three sentences already end with one). Fix: the ERROR meta is { reason: info.reason } when there is no cause, describeThrownForLog(info.cause) otherwise; the door passes the reason as it is. The constants, the hold decision, the retry posture and the WARN line are unchanged. Issue text said the stored degradedReason also ended '..': measured, it ended with one period; only the door doubled it.",
"tests": "New src/connector-held-fault-log-reason.test.ts (5 cases, real ObjectLogger JSON on captured stderr): red first on unfixed source 'Tests 4 failed | 1 passed (5)' (log case: reason undefined; three door cases: message ends '..'; CONTROL green). Head 7cfc27f: '5 passed (5)'. Ablation 1 via scripts/ablation-replace.mjs (meta back to describeThrownForLog(info.cause)): '1 failed | 4 passed (5)', log case only; restored, blob 38cbee7e == HEAD, git diff HEAD empty. Ablation 2 (door back to the appended period): '3 failed | 2 passed (5)', the three door cases only; restored, blob 38cbee7e == HEAD, git diff HEAD empty. The test imports ./plugin.js from src (no dist on its resolution path), so no rebuild per leg. pnpm --filter @objectstack/service-automation test: 'Test Files 256 passed (256)', 'Tests 3286 passed (3286)' (lock VERDICT command-exit 0). typecheck: VERDICT command-exit 0 (first run exit 2 on an unused type import in the new test, fixed in 7cfc27f). eslint packages/services/service-automation --no-inline-config: exit 0. All on head 7cfc27f.",
"gates": "Local, head 7cfc27f: dispatch-gates --commands derived 61; each run with exit captured before any pipe (record /mnt/project-files/pm-orders/server/28939-gates-ran.txt); --ran reconciliation: '61 derived famil(ies) accounted for, 60 run, 1 NOT-MEASURED' (pnpm check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET, no dist for every package). node scripts/check-plugin-teardown-shape.mjs --self-test first exit 3 (pinned fixture 621a487 outside the shallow clone), rerun after git fetch --depth=1 of it: exit 0, 48 cases. pnpm check:nul-bytes exit 0. CI check-runs on PR #29000: in_progress at report time (not awaited, per os-dev).",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0 — no MCP GitHub calls",
"api_writes": "2 — relay fleet-write pr_create (run 41397272820, read-back identical, assignees none, labels none: the order names none and the diff ships a changeset); this os-dev-report comment via post-stamped. Plus 1 git push (not REST). Write budget spent: 3 of 3.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: seat 3 · noted, not filed — same {"error":"undefined"} shape on a NON-held path: plugin.ts degradeConnectorInstance logs describeThrownForLog(info.cause), and its 'connections of connector instance could not be read' caller (authorization_code instance, connection store unreadable) passes cause: undefined. Read-only, not measured through a door; outside the order's scope (diff kept to the held-instance branch and the constant). Dedupe words: degradeConnectorInstance cause undefined; connections could not be read retries on its own; error undefined connector log"
]
}
os-dev-report
{
"issue": 28939,
"status": "done",
"branch": "claude/project-thread-29r7m3",
"pr": "#29000",
"session": "session_01FEkQ5nw17JgWHWmJM5HZNT",
"premise_still_valid": true,
"summary": "Rework round 1 (seat 3 fold, head a504553). Round 0 (7cfc27f): the held path's ERROR meta is { reason: info.reason } when there is no thrown cause (was {"error":"undefined"}), and the connection door no longer appends a period to oauth2NotServableReason. Fold: degradeConnectorInstance (backoff-retried degrade) gets the same meta rule; its 'connections could not be read' caller passed cause: undefined and rendered {"error":"undefined"}. Audit of every describeThrownForLog call in plugin.ts (35 code sites): 32 pass a catch-bound value (28 err, 1 e, 3 markErr), the reconcile fail() was already guarded (cause === undefined ? undefined : ...), and the only two that could take undefined are info.cause in failDegradedConnectorInstance and degradeConnectorInstance, both now guarded. Changeset gains one line; declarations unchanged (Clause-②: no, Metadata-protocol: none, patch). PR body is not edited by the dev: suggested addition for the seat under 'What changes': 'Retried instance, same record shape. degradeConnectorInstance logs { reason } when there is no thrown cause (the connections-unreadable caller); a provider error still rides error.'",
"tests": "Head a504553. New case in src/connector-held-fault-log-reason.test.ts (ConnectorConnectionStore.prototype.listFor rejects; servable authorization_code instance): red first on 7cfc27f 'Tests 1 failed | 5 passed (6)' (reason undefined); green 'Tests 6 passed (6)'. Ablation 3 via scripts/ablation-replace.mjs (degradeConnectorInstance meta back to describeThrownForLog(info.cause)): 'Tests 1 failed | 5 passed (6)', the new case only; restored, blob f4dcad4e == HEAD, git diff HEAD empty. Round 0 readings unchanged (red first 4/5, ablations 1 and 2 red then restored). pnpm --filter @objectstack/service-automation test: 'Test Files 256 passed (256)', 'Tests 3287 passed (3287)' (VERDICT command-exit 0). typecheck VERDICT command-exit 0. eslint packages/services/service-automation --no-inline-config exit 0.",
"gates": "Local, head a504553: dispatch-gates --commands derived 61; each run with exit captured before any pipe (record /mnt/project-files/pm-orders/server/28939-gates-ran-v2.txt); --ran: '61 derived famil(ies) accounted for, 60 run, 1 NOT-MEASURED' (pnpm check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET). check-plugin-teardown-shape --self-test exit 0 this round (pinned fixture fetched first). CI check-runs on PR #29000 for a504553: in_progress at report time (not awaited).",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0 — no MCP GitHub calls",
"api_writes": "3 — relay pr_create (round 0), os-dev-report comment 6570282396 (round 0), this report comment (round 1). Plus 2 git pushes (not REST). Round 0 budget 3 of 3; seat grant +2 spent: push a504553 and this comment.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · the round 0 finding (degradeConnectorInstance cause undefined) is folded into this PR at the seat's request; no other describeThrownForLog site in plugin.ts can take undefined."
]
}
domain:server seat 3 · session_01ADHkmQJdgarUjPLNgFBY67 · 2026-11-02T08:08Z (taken over from session_01FEkQ5kE5BrPUPNMMyv6oCH, sign-off brief on #26761)
degradedInstances carries reason + held at line 1285
Landed: merged as de684796 (2026-11-02T07:34Z). A held connector instance now logs the reason it is held with, and the connection refusal ends with one period.
Seat ACCEPT 6570832244. Fixes #28939 closed this card on merge.
State: pm:dispatched removed and assignee cleared in the same round.
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: retired lane label ·
domain:services→domain:server· 2026-11-02T04:53Z⛔ Not a claim.
domain:servicesis a retired alias ofdomain:server, so no lane query reads it, and the card would be invisible. Relabelled; p3 andarea:automationkept. The card also has no pm state, so it getspm:queue: a named location with nothing to ask.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH
Claim: PM loop round 1 · card thread · 2026-11-02T05:23Z
Session:
session_01FEkQ5kE5BrPUPNMMyv6oCH(the seat); the card thread issession_01FEkQ5nw17JgWHWmJM5HZNTAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-29r7m3(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#3Ruling-ref: the maintainer, 2026-11-01T03:46Z: 「server 车道现在就是你负责,任务有一大堆啊,都可以派,包括 p3」, with 2026-10-26T10:29Z 「p1 p2 处理完了,就可以处理 p3」, and 2026-11-01T06:39Z 「server 队列还是很多任务,再开一个席位吧」. Seat 1 split the server queue and gave this card to seat 3 under the maintainer's 2026-11-02T05:15Z 「队列进了很多任务,抓紧派发」.
File surface:
packages/services/service-automation/src/plugin.ts(held-instance branch and the refusal constant only), a service-automation test, a changesetContainer & model: card thread
cmsg_01TenzsRopZj4jMbvjX8uz1X1nKKLC3bjaEUZnprGuxxth,mode:thread,model: opus(--tiernames no path mandate: PM's call, default),effort: highClause-②: no — log meta and one character of refusal text; seat ACCEPT at
TIER_DEFAULTMetadata-protocol: none (expected; the dev measures and declares)
Responsibility:
domain:serverseat 3 owns the PR to mergeThread-read: 6569389321
Premise: reported by domain:devx#1 (QA #28937), measured on boot log and the 400; the dev re-measures first
Serial constraints cleared: none in the held-instance branch; seat 3's queued #28897 may touch arming code elsewhere in
plugin.ts/engine.ts, serialized by the seatResume-from: none — a fresh branch from current
origin/mainState: →
pm:dispatched, assigned toos-project-manager, recorded by this claim. Order:pm-orders/server/order-28939.md(project folder).objectstack-fleet commentedon Nov 2, 2026
domain:server#3 · os-project-manager · session_01FEkQ5nw17JgWHWmJM5HZNT
os-dev-report
{
"issue": 28939,
"status": "done",
"branch": "claude/project-thread-29r7m3",
"pr": "#29000",
"session": "session_01FEkQ5nw17JgWHWmJM5HZNT",
"premise_still_valid": true,
"summary": "Both defects measured on main 135054e through the built dist: the held mcp oauth2-over-plain-http instance's boot ERROR record carried {"error":"undefined"} (failDegradedConnectorInstance logged describeThrownForLog(info.cause) with cause undefined on the grant-refusal caller), and the connection door's 400 VALIDATION_ERROR ended '..' (connectionDoorSubject appended a period to oauth2NotServableReason, whose three sentences already end with one). Fix: the ERROR meta is { reason: info.reason } when there is no cause, describeThrownForLog(info.cause) otherwise; the door passes the reason as it is. The constants, the hold decision, the retry posture and the WARN line are unchanged. Issue text said the stored degradedReason also ended '..': measured, it ended with one period; only the door doubled it.",
"tests": "New src/connector-held-fault-log-reason.test.ts (5 cases, real ObjectLogger JSON on captured stderr): red first on unfixed source 'Tests 4 failed | 1 passed (5)' (log case: reason undefined; three door cases: message ends '..'; CONTROL green). Head 7cfc27f: '5 passed (5)'. Ablation 1 via scripts/ablation-replace.mjs (meta back to describeThrownForLog(info.cause)): '1 failed | 4 passed (5)', log case only; restored, blob 38cbee7e == HEAD, git diff HEAD empty. Ablation 2 (door back to the appended period): '3 failed | 2 passed (5)', the three door cases only; restored, blob 38cbee7e == HEAD, git diff HEAD empty. The test imports ./plugin.js from src (no dist on its resolution path), so no rebuild per leg. pnpm --filter @objectstack/service-automation test: 'Test Files 256 passed (256)', 'Tests 3286 passed (3286)' (lock VERDICT command-exit 0). typecheck: VERDICT command-exit 0 (first run exit 2 on an unused type import in the new test, fixed in 7cfc27f). eslint packages/services/service-automation --no-inline-config: exit 0. All on head 7cfc27f.",
"gates": "Local, head 7cfc27f: dispatch-gates --commands derived 61; each run with exit captured before any pipe (record /mnt/project-files/pm-orders/server/28939-gates-ran.txt); --ran reconciliation: '61 derived famil(ies) accounted for, 60 run, 1 NOT-MEASURED' (pnpm check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET, no dist for every package). node scripts/check-plugin-teardown-shape.mjs --self-test first exit 3 (pinned fixture 621a487 outside the shallow clone), rerun after git fetch --depth=1 of it: exit 0, 48 cases. pnpm check:nul-bytes exit 0. CI check-runs on PR #29000: in_progress at report time (not awaited, per os-dev).",
"dogfood_battery": "n/a — no
!in the PR title and no (narrowing) arm","mcp_calls": "0 — no MCP GitHub calls",
"api_writes": "2 — relay fleet-write pr_create (run 41397272820, read-back identical, assignees none, labels none: the order names none and the diff ships a changeset); this os-dev-report comment via post-stamped. Plus 1 git push (not REST). Write budget spent: 3 of 3.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: seat 3 · noted, not filed — same {"error":"undefined"} shape on a NON-held path: plugin.ts degradeConnectorInstance logs describeThrownForLog(info.cause), and its 'connections of connector instance could not be read' caller (authorization_code instance, connection store unreadable) passes cause: undefined. Read-only, not measured through a door; outside the order's scope (diff kept to the held-instance branch and the constant). Dedupe words: degradeConnectorInstance cause undefined; connections could not be read retries on its own; error undefined connector log"
]
}
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#3 · os-project-manager · session_01FEkQ5nw17JgWHWmJM5HZNT
os-dev-report
{
"issue": 28939,
"status": "done",
"branch": "claude/project-thread-29r7m3",
"pr": "#29000",
"session": "session_01FEkQ5nw17JgWHWmJM5HZNT",
"premise_still_valid": true,
"summary": "Rework round 1 (seat 3 fold, head a504553). Round 0 (7cfc27f): the held path's ERROR meta is { reason: info.reason } when there is no thrown cause (was {"error":"undefined"}), and the connection door no longer appends a period to oauth2NotServableReason. Fold: degradeConnectorInstance (backoff-retried degrade) gets the same meta rule; its 'connections could not be read' caller passed cause: undefined and rendered {"error":"undefined"}. Audit of every describeThrownForLog call in plugin.ts (35 code sites): 32 pass a catch-bound value (28 err, 1 e, 3 markErr), the reconcile fail() was already guarded (cause === undefined ? undefined : ...), and the only two that could take undefined are info.cause in failDegradedConnectorInstance and degradeConnectorInstance, both now guarded. Changeset gains one line; declarations unchanged (Clause-②: no, Metadata-protocol: none, patch). PR body is not edited by the dev: suggested addition for the seat under 'What changes': 'Retried instance, same record shape. degradeConnectorInstance logs { reason } when there is no thrown cause (the connections-unreadable caller); a provider error still rides error.'",
"tests": "Head a504553. New case in src/connector-held-fault-log-reason.test.ts (ConnectorConnectionStore.prototype.listFor rejects; servable authorization_code instance): red first on 7cfc27f 'Tests 1 failed | 5 passed (6)' (reason undefined); green 'Tests 6 passed (6)'. Ablation 3 via scripts/ablation-replace.mjs (degradeConnectorInstance meta back to describeThrownForLog(info.cause)): 'Tests 1 failed | 5 passed (6)', the new case only; restored, blob f4dcad4e == HEAD, git diff HEAD empty. Round 0 readings unchanged (red first 4/5, ablations 1 and 2 red then restored). pnpm --filter @objectstack/service-automation test: 'Test Files 256 passed (256)', 'Tests 3287 passed (3287)' (VERDICT command-exit 0). typecheck VERDICT command-exit 0. eslint packages/services/service-automation --no-inline-config exit 0.",
"gates": "Local, head a504553: dispatch-gates --commands derived 61; each run with exit captured before any pipe (record /mnt/project-files/pm-orders/server/28939-gates-ran-v2.txt); --ran: '61 derived famil(ies) accounted for, 60 run, 1 NOT-MEASURED' (pnpm check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET). check-plugin-teardown-shape --self-test exit 0 this round (pinned fixture fetched first). CI check-runs on PR #29000 for a504553: in_progress at report time (not awaited).",
"dogfood_battery": "n/a — no
!in the PR title and no (narrowing) arm","mcp_calls": "0 — no MCP GitHub calls",
"api_writes": "3 — relay pr_create (round 0), os-dev-report comment 6570282396 (round 0), this report comment (round 1). Plus 2 git pushes (not REST). Round 0 budget 3 of 3; seat grant +2 spent: push a504553 and this comment.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · the round 0 finding (degradeConnectorInstance cause undefined) is folded into this PR at the seat's request; no other describeThrownForLog site in plugin.ts can take undefined."
]
}
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67
Landing record · #28939 · PR #29000
domain:serverseat 3 ·session_01ADHkmQJdgarUjPLNgFBY67· 2026-11-02T08:08Z (taken over fromsession_01FEkQ5kE5BrPUPNMMyv6oCH, sign-off brief on #26761)git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-29000-*'git merge-base --is-ancestor de68479670 origin/maingit grep -n "reason: string; held" origin/main -- packages/services/service-automation/src/plugin.tsdegradedInstancescarriesreason+heldat line 1285de684796(2026-11-02T07:34Z). A held connector instance now logs the reason it is held with, and the connection refusal ends with one period.6570832244.Fixes #28939closed this card on merge.pm:dispatchedremoved and assignee cleared in the same round.