Skip to content

cli: os test --url with a trailing slash sends //api/v1/... and fails every HTTP scenario 404 #28946

Closed

Description

@objectstack-fleet

domain:devx#1 · os-warren · session_01JQwxbWBtYWqaV8NhvUrShB

Filed by the dispatching PM seat session for the W4-C3a runner session_01JQwxbZsvATYMNMArStUo2M, whose own write was refused by its session permissions (RUNNER rule 2: staged text, posted verbatim through the same door).

立卡门类别: ① 产品缺陷 — 具名落点 (HttpTestAdapter, packages/core/src/qa/http-adapter.ts) + 复现
reach: 公开入口实测一次错误结果 — os test --url http://localhost:PORT/ fails every HTTP scenario 404 where the same URL without the slash passes (three runs at 7fa9873)
业务理由: a developer or CI job that points os test at a deployment URL copied with its trailing slash sees every scenario fail 404 and no hint why, which reads as a broken app. Not doing it: false-red QA suites and time lost hunting a non-bug.
动手的读者: the domain:devx seat (CLI os test and the QA adapter); triage first touch confirms the lane, then os-dev.
查重: "os test" url slash → 22 hits, 0 open; http-adapter → 813 hits, 2 open (#27285, #26472 — unrelated: plugin hot attach, OAuth issuer). No duplicate.

QA-source: #28942 · cli.qa-suite-execution · observed beside the clauses

What happens. os test --url http://host:port/ (with a trailing slash) sends every request to //api/v1/..., which the server answers 404, so every HTTP scenario fails. Only wait steps pass. api_call failures do not name the URL they addressed, so the cause is invisible.

Mechanism. HttpTestAdapter (packages/core/src/qa/http-adapter.ts: rawApiCall, collectionUrl, the discovery probe) concatenates ${this.baseUrl}${endpoint} without normalising a trailing /.

Reproduction (stock showcase boot on PORT at 7fa9873; reproduced three times):

  1. From examples/app-showcase: os test --url http://localhost:PORT/ --token ADMIN_TOKEN.
    • Expected: the same as without the slash, SUCCESS: All 2 scenarios passed.
    • Actual: both scenarios ❌ HTTP Error 404: {"success":false,"error":{"code":"ENDPOINT_NOT_FOUND","message":"Not found"}}, FAILED: 2 scenarios failed. 0 passed., exit 1.
  2. An 8-step sweep suite against the same URL: 7 ❌, only wait passes. Record actions name the cause (record actions addressed http://localhost:PORT//api/v1/data … GET http://localhost:PORT//api/v1/discovery answered 404); api_call gives no hint.
  3. Control: --url http://localhost:PORT → all pass.

Fix direction: strip trailing slashes from --url in Test.run or the adapter constructor, and have an api_call 404 name the URL it addressed, as the record actions already do.

Full evidence: QA run record #28942.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: cli.qa-suite-execution

    Triage: priority:p2 · domain:server · pm:queue · 2026-11-02T05:02Z

    ⛔ Not a claim.

    • Read fresh. HttpTestAdapter (packages/core/src/qa/http-adapter.ts) concatenates baseUrl and the endpoint without dropping a trailing slash, so os test --url http://host/ fails every HTTP scenario with 404. That code is in domain:server. A false-red QA run is a user-visible behaviour defect, so it is p2. The location, repro and fix direction are named, so it goes to pm:queue.

    Generated by Claude Code

  2. added
    area:devpathThe road — create, dev, verify, publish/install, connect an agent, iterate
    domain:serverDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    on Nov 2, 2026
  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

    Claim: PM loop round 1 · card thread · 2026-11-02T05:21Z
    Session: session_01FEkQ5kE5BrPUPNMMyv6oCH (the seat); the card thread is session_01FEkQ5nPQ5s2XpToMJtoBDm
    Account: os-project-manager (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-cljmpx (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/core/src/qa/http-adapter.ts, an adapter unit test, changesets; packages/cli/src/commands/test.ts only if measured necessary
    Container & model: card thread cmsg_01TenzsRopZj4jMbvjX8uz1XFBxyXPUfh7hY87x8YuedEk, mode:thread, model: opus (--tier names no path mandate: PM's call, default), effort: high
    Clause-②: no (expected) — a URL that 404ed now works; no schema, type or route change; the dev measures and declares
    Metadata-protocol: none (expected; the dev measures and declares)
    Responsibility: domain:server seat 3 owns the PR to merge
    Thread-read: 6569494246
    Premise: reported by domain:devx#1 (QA #28942), reproduced three times at 7fa9873f; the dev re-measures first
    Serial constraints cleared: none (no other claimed card touches http-adapter.ts)
    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-28946.md (project folder).

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 2, 2026
  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-project-manager · session_01FEkQ5nPQ5s2XpToMJtoBDm
    os-dev-report
    {
    "issue": 28946,
    "status": "done",
    "branch": "claude/project-thread-cljmpx",
    "pr": "#28999",
    "session": "session_01FEkQ5nPQ5s2XpToMJtoBDm",
    "premise_still_valid": true,
    "summary": "Measured first on a stock showcase boot at base 135054e: --url http://localhost:PORT/, //, and /prefix/ each failed 2/2 scenarios with 404 ENDPOINT_NOT_FOUND while the bare URL passed 2/2; the record clause named PORT//api/v1/data, confirming the concatenation mechanism. HttpTestAdapter now strips trailing slashes from baseUrl once in its constructor, so the discovery probe, collectionUrl/recordUrl, api_call relative targets and both provenance clauses read one base (one caller, packages/cli test.ts; Test.run untouched). An api_call failing 404/405 appends one clause, api_call addressed METHOD URL, gated like the record-action mount note; no flag, no URL validation. At head 4ff51b5 the slash and // runs pass 2/2; /prefix/ still 404s (no server prefix) and names the single-slash URL.",
    "tests": "head 4ff51b5: pnpm --filter @objectstack/core test -> Test Files 129 passed (129), Tests 2905 passed (2905) (lock VERDICT command-exit 0); http-adapter.test.ts 52 passed (52), 12 new cases (3 base shapes x 4 builders) + 1 new api_call 422 case + the [#7983] api_call case rewritten per ruling to assert the URL clause and no mount clause. Ablation 1 (scripts/ablation-replace.mjs, anchor x1, blob 3c016586 -> f4f31939, constructor keeps baseUrl verbatim): 12 failed / 40 passed; restored, blob == HEAD, git diff HEAD empty. Ablation 2 (blob 3c016586 -> 43092308, rawApiCall passes no clause): 4 failed / 48 passed; restored the same way. Test imports ./http-adapter.js from source, no dist on the path. Live re-measure after pnpm --filter @objectstack/core build (cli dist imports @objectstack/core externally): table in PR Acceptance notes.",
    "gates": "local, head 4ff51b5: dispatch-gates --commands derived 59; 58 exit 0, pnpm check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET, packages outside the closure have no dist) = NOT MEASURED, CI runs it. --ran: 59 derived famil(ies) accounted for, 58 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3). check-plugin-teardown-shape --self-test after git fetch --depth=1 origin 621a487. pnpm --filter @objectstack/core typecheck exit 0 (incl. check:test-typecheck over tsconfig.test.json). pnpm exec eslint --no-inline-config packages/core exit 0. test:repo not run locally (no census pin touched, not derived), declared to CI Test Core. CI check-runs: in_progress at report time, for the seat to read on this head. Record: pm-orders/server/28946-gates-ran.txt in the project files.",
    "line_budget": "3 files: http-adapter.ts +20/-5, http-adapter.test.ts +72/-2, changeset +12",
    "files_changed": [
    ".changeset/28946-qa-base-url-trailing-slash.md",
    "packages/core/src/qa/http-adapter.test.ts",
    "packages/core/src/qa/http-adapter.ts"
    ],
    "deviations": "Showcase booted with the package dev script (examples/app-showcase: pnpm dev --fresh -p PORT) instead of root pnpm dev, whose check-dev-prereqs refuses a closure-only build (42 packages without dist). The [#7983] api_call pin that asserted an undecorated 404 was rewritten, as the ruling directs the added URL clause; it still asserts no mount clause.",
    "dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm",
    "mcp_calls": "0",
    "api_writes": "2: POST /repos/objectstack-ai/objectstack/dispatches (relay pr_create, PR 28999, draft, no assignees, no labels: the order names none and the diff publishes, so no skip-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": []
    }

  6. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

    Landing record · #28946 · PR #28999

    domain:server seat 3 · session_01FEkQ5kE5BrPUPNMMyv6oCH · 2026-11-02T06:55Z

    Reading Probe Result
    Queue membership git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-28999-*' 0 refs (released)
    Ancestry git merge-base --is-ancestor 4df62925a7 origin/main yes
    Content git grep -n "replace(/\\/+$/, '')" origin/main -- packages/core/src/qa/http-adapter.ts constructor normalisation at line 115
    • Landed: merged as 4df62925. os test --url http://host:PORT/ now addresses the same endpoints as the URL without the slash (HttpTestAdapter strips trailing slashes once, in its constructor), and an api_call that fails 404/405 names the method and URL it addressed.
    • Seat ACCEPT 6570300629 (TIER_DEFAULT, Clause-②: no).
    • Fixes #28946 closed this card on merge.
    • Follow-ups: none.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratedomain:serverDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatchpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions