release-pipeline-status: an unimplemented adapter primitive is indistinguishable from a broken helper (exit 127), making promote's none branch unreachable #55

Open
opened 2026-08-24 09:06:33 +00:00 by jbr870 · 1 comment
Owner

Observation

release-pipeline-status.sh cannot distinguish "this project has no CI" from "the helper is
broken"
, and reports the second shape for the first case.

Hit for real during /dev:promote on this repo (2026-08-24, shipping #26 + #50 as dev--v0.1.6):

$ release-pipeline-status.sh --ref dev--v0.1.6
_shared/procedures/bin/_lib.sh: line 108: .../tea-cli/bin/pipeline_status.sh: No such file or directory
$ echo $?
127

The tea-cli adapter implements 11 primitives and pipeline_status is not among them — the file
simply does not exist. _lib.sh invokes the adapter primitive by path with no existence check, so
the caller gets a shell-level 127 and a message naming an internal helper path, which is not
information the calling skill can act on.

Why it matters

/dev:promote Step 5 has an explicit, correct branch for exactly this situation:

none (e.g. local-fs, or a repo with no CI) is not a failure — it means there is no pipeline
to gate on, so the required deploy_check (Step 6) carries the whole "is it live?" decision.

That branch is unreachable on tea-cli. The skill text anticipates the case; the helper cannot
express it. An orchestrator following Step 5 literally sees a non-zero exit and should route to
Step 8 — escalate a blocking decision and ship nothing, which is the wrong answer for a repo
that legitimately has no CI. I only proceeded because I could establish "no pipeline exists"
independently (no .gitea/workflows, no .github/workflows, no such adapter primitive) — i.e. by
going around the helper, which is not a procedure.

This is the same family as #49 (a gate's failure output must identify the failure) and #54 (a
reader must fail with a diagnosis, not a stack error): an absent capability and a broken call
must not look alike.

Recurrence

Mechanical and permanent for every tea-cli project — the primitive is not merely unimplemented
for this repo, it is absent from the adapter. glab-cli/gh-cli should be checked too: if they
implement it, the contract is silently adapter-dependent; if they don't, Step 5 is dead code on
three of four adapters. local-fs is named in the skill text as returning none, so at least one
adapter is expected to answer.

Hypothesis

  1. The forge contract should state whether pipeline_status is required or optional. If
    optional, _lib.sh should detect the missing primitive and return the contract's none — an
    adapter that cannot report pipelines is reporting "no pipeline", which is exactly none.
  2. _lib.sh must not exec an adapter primitive by path without checking it exists. Every
    primitive dispatch has this shape, so any unimplemented primitive fails the same opaque way.
  3. If none-on-absent is judged too lenient (it could mask a genuinely broken adapter install),
    the alternative is an explicit unsupported state plus a contract rule that promote treats
    unsupported like none — but says so out loud in the Phase Outcome.

Suggested lane

Full pipeline — helper behaviour plus a forge-contract change across four adapters.

Filed 2026-08-24 from the /dev:promote run that shipped #26 and #50.

## Observation `release-pipeline-status.sh` cannot distinguish **"this project has no CI"** from **"the helper is broken"**, and reports the second shape for the first case. Hit for real during `/dev:promote` on this repo (2026-08-24, shipping #26 + #50 as `dev--v0.1.6`): ``` $ release-pipeline-status.sh --ref dev--v0.1.6 _shared/procedures/bin/_lib.sh: line 108: .../tea-cli/bin/pipeline_status.sh: No such file or directory $ echo $? 127 ``` The `tea-cli` adapter implements 11 primitives and `pipeline_status` is not among them — the file simply does not exist. `_lib.sh` invokes the adapter primitive by path with no existence check, so the caller gets a shell-level 127 and a message naming an **internal helper path**, which is not information the calling skill can act on. ## Why it matters `/dev:promote` Step 5 has an explicit, correct branch for exactly this situation: > `none` (e.g. local-fs, or a repo with no CI) is **not** a failure — it means there is no pipeline > to gate on, so the required `deploy_check` (Step 6) carries the whole "is it live?" decision. That branch is unreachable on `tea-cli`. The skill text anticipates the case; the helper cannot express it. An orchestrator following Step 5 literally sees a non-zero exit and should route to **Step 8 — escalate a blocking decision and ship nothing**, which is the wrong answer for a repo that legitimately has no CI. I only proceeded because I could establish "no pipeline exists" independently (no `.gitea/workflows`, no `.github/workflows`, no such adapter primitive) — i.e. by going around the helper, which is not a procedure. This is the same family as #49 (a gate's failure output must identify the failure) and #54 (a reader must fail with a diagnosis, not a stack error): **an absent capability and a broken call must not look alike.** ## Recurrence Mechanical and permanent for every `tea-cli` project — the primitive is not merely unimplemented for this repo, it is absent from the adapter. `glab-cli`/`gh-cli` should be checked too: if they implement it, the contract is silently adapter-dependent; if they don't, Step 5 is dead code on three of four adapters. `local-fs` is named in the skill text as returning `none`, so at least one adapter is expected to answer. ## Hypothesis 1. **The forge contract should state whether `pipeline_status` is required or optional.** If optional, `_lib.sh` should detect the missing primitive and return the contract's `none` — an adapter that cannot report pipelines is reporting "no pipeline", which is exactly `none`. 2. **`_lib.sh` must not exec an adapter primitive by path without checking it exists.** Every primitive dispatch has this shape, so any unimplemented primitive fails the same opaque way. 3. If `none`-on-absent is judged too lenient (it could mask a genuinely broken adapter install), the alternative is an explicit `unsupported` state plus a contract rule that promote treats `unsupported` like `none` — but says so out loud in the Phase Outcome. ## Suggested lane **Full pipeline** — helper behaviour plus a forge-contract change across four adapters. *Filed 2026-08-24 from the `/dev:promote` run that shipped #26 and #50.*
Author
Owner

Scope folded into #47 (2026-08-25, /dev:requirements session devwork-47-55-pipeline-status-identity).

This issue's requirement is now carried by #47's PREQ. The two were folded because they are one defect in
two costumes: the same helper, release-pipeline-status.sh, returning an answer nobody can attribute —
to a commit (#47) or to a stated absence (this issue). Splitting them would have meant changing the same
{state, url} response shape twice.

What #47 now carries from here:

  • _lib.sh's _prim dispatch stops invoking an adapter primitive by path without checking it exists —
    confirmed still true at HEAD: _prim() { "$ADAPTER_BIN/$1.sh" "${@:2}"; }.
  • An absent primitive answers by kind: a capability-gated op (the release and wiki ops — the contract
    already classes pipeline_status among them in §2.2) resolves to a stated unsupported; a required
    op fails loudly with a diagnosis naming the adapter, the primitive and where it was looked for. This is
    hypothesis 1 + 2 from this issue, with hypothesis 3's "say it out loud" adopted: /dev:promote records
    the skipped pipeline gate and its reason in the Phase Outcome rather than proceeding silently.
  • No shell-level error and no internal helper path reaches the operator or the calling skill.
  • /dev:promote Step 5's no-CI branch becomes reachable by following the procedure, instead of by an
    operator establishing the absence independently.

The adapter census this issue asked for, resolved:

adapter release ops on disk manifest
glab-cli all six present pipeline_status: native
local-fs all six present (filesystem simulation) pipeline_status: polyfill
tea-cli none release ops absent from the manifest entirely
gh-cli none release ops absent from the manifest entirely

So Step 5 was dead code on two of four adapters, and gh-cli cannot run /dev:promote at all. That
second finding is filed separately as #69 (sibling of #47) rather than folded, because it is
implementation work rather than honesty work.

Deliberately not done: implementing pipeline_status for tea-cli. The Forgejo instance is being
decommissioned in favour of GitHub, so a primitive written against it is redundant on arrival — and this
issue's own hypothesis is that an adapter which cannot report pipelines is reporting "no pipeline".
Known consequence, recorded as a finding on #47: a tea-cli project that does run Gitea Actions would now
get a stated unsupported and its real pipelines would go unwatched.

This issue is left open and linked to #47 as its folded source; it is not being worked independently.

**Scope folded into #47** (2026-08-25, `/dev:requirements` session `devwork-47-55-pipeline-status-identity`). This issue's requirement is now carried by #47's PREQ. The two were folded because they are one defect in two costumes: the same helper, `release-pipeline-status.sh`, returning an answer nobody can attribute — to a commit (#47) or to a stated absence (this issue). Splitting them would have meant changing the same `{state, url}` response shape twice. **What #47 now carries from here:** - `_lib.sh`'s `_prim` dispatch stops invoking an adapter primitive by path without checking it exists — confirmed still true at HEAD: `_prim() { "$ADAPTER_BIN/$1.sh" "${@:2}"; }`. - An absent primitive answers **by kind**: a capability-gated op (the release and wiki ops — the contract already classes `pipeline_status` among them in §2.2) resolves to a stated `unsupported`; a **required** op fails loudly with a diagnosis naming the adapter, the primitive and where it was looked for. This is hypothesis 1 + 2 from this issue, with hypothesis 3's "say it out loud" adopted: `/dev:promote` records the skipped pipeline gate and its reason in the Phase Outcome rather than proceeding silently. - No shell-level error and no internal helper path reaches the operator or the calling skill. - `/dev:promote` Step 5's no-CI branch becomes reachable by following the procedure, instead of by an operator establishing the absence independently. **The adapter census this issue asked for, resolved:** | adapter | release ops on disk | manifest | |---|---|---| | `glab-cli` | all six present | `pipeline_status: native` | | `local-fs` | all six present (filesystem simulation) | `pipeline_status: polyfill` | | `tea-cli` | **none** | release ops absent from the manifest entirely | | `gh-cli` | **none** | release ops absent from the manifest entirely | So Step 5 was dead code on **two of four** adapters, and `gh-cli` cannot run `/dev:promote` at all. That second finding is filed separately as **#69** (sibling of #47) rather than folded, because it is implementation work rather than honesty work. **Deliberately not done:** implementing `pipeline_status` for `tea-cli`. The Forgejo instance is being decommissioned in favour of GitHub, so a primitive written against it is redundant on arrival — and this issue's own hypothesis is that an adapter which cannot report pipelines *is* reporting "no pipeline". Known consequence, recorded as a finding on #47: a `tea-cli` project that does run Gitea Actions would now get a stated `unsupported` and its real pipelines would go unwatched. This issue is left **open** and linked to #47 as its folded source; it is not being worked independently.
Sign in to join this conversation.
No description provided.