Skip to content

Release v0.1.0 - #2

Merged
lchoquel merged 16 commits into
mainfrom
release/v0.1.0
Jul 1, 2026
Merged

Release v0.1.0#2
lchoquel merged 16 commits into
mainfrom
release/v0.1.0

Conversation

@lchoquel

@lchoquel lchoquel commented Jul 1, 2026

Copy link
Copy Markdown
Member

Release v0.1.0

The initial public release of pipelex-sdk (import package pipelex_sdk) — the Python counterpart of @pipelex/sdk, built by inheritance on the mthds protocol base. This is the first version; nothing was previously published to PyPI. Merging this PR into main triggers publish.yml, which builds the wheel, publishes it to PyPI as pipelex-sdk via Trusted Publishing, and cuts the Sigstore-signed GitHub release.

Changelog

[v0.1.0] - 2026-07-01

The initial public surface of pipelex-sdk — the Python counterpart of @pipelex/sdk, built by inheritance on the mthds protocol base. Surface-complete against the TypeScript SDK (see docs/architecture.md → "Parity with @pipelex/sdk"); the /v1/build/* helpers and the WorkOS org-switch are consciously out of scope for this release.

Added

  • Initial repository scaffold: packaging (pyproject.toml), tooling (Makefile, ruff/pyright/mypy/pylint config mirroring mthds-python), and the empty pipelex_sdk package.
  • GitHub Actions CI/CD mirroring mthds-python, adapted to the Pipelex org and the pipelex-sdk PyPI distribution: PR gates (lint-check, tests-check, package-check, changelog-check, version-check, guard-branches, cla) across the full Python matrix, plus publish.yml (build → PyPI Trusted Publishing → signed GitHub Release) on push to main. Root CLA.md and docs/ci-cd.md added alongside.
  • PipelexAPIClient (subclass of mthds's MthdsAPIClient): Pipelex-branded construction (resolves PIPELEX_API_KEY / PIPELEX_API_URL, falling back to the mthds resolver; token optional for anonymous access; host-only base-URL validation; origin URL for health).
  • Transport extension layer: _request_product, _request_json, transport-failure mapping to ApiUnreachableError, and the problem+json error-body parser.
  • Errors: ApiResponseError (with the RFC 9457 code discriminant) and ApiUnreachableError, both deriving from the protocol-base PipelineRequestError.
  • Durable run lifecycle (pipelex_sdk/runs.py + client methods): owned run-lifecycle models and the polling surface get_run_status / get_run_result / wait_for_result.
  • start_and_wait self-heals across hosted and bare runners (durable start+poll on the hosted API, blocking POST /v1/execute fallback on a bare runner).
  • Lifecycle errors RunFailedError, RunTimeoutError, RunLifecycleUnavailableError; RunStillRunningError re-exported from mthds.
  • Pipelex product surface (pipelex_sdk/product_models.py + client methods): user profile, methods catalog CRUD, organizations, billing, Pipelex API keys, the gateway inference key, onboarding, storage, and run records.
  • Pipelex validation models (pipelex_sdk/validation_models.py): the SDK owns the Pipelex-branded narrowing of the /v1/validate 200-diagnostic union (resolves the brand-layering follow-up Scope the contract-only usage claims and fix the run-usage example #9).
  • validate override + validate_files, health(), execute override + PipelineExecuteTimeoutError.
  • __version__ (pipelex_sdk.version), derived from installed distribution metadata.

Changed

  • Requires mthds>=0.6.1 (protocol base floor).
  • CHANGELOG.md version headers use the workspace-wide ## [vX.Y.Z] convention (matching mthds-python / pipelex-sdk-js), which the changelog/publish workflows key off.

Fixed

  • PipelexAPIClient honors an explicit api_token="" as a request for anonymous access even when a credential is configured. Credential resolution tests is not None rather than truthiness, so the first present layer wins.
  • guard-branches.yml workflow-protection job checks out the PR head (ref: pull_request.head.sha) so its git diff detects fork edits to .github/workflows/*; its author-association gate is a maintainer allow-list covering FIRST_TIME_CONTRIBUTOR / FIRST_TIMER / NONE.
  • tests-check.yml no longer grants id-token: write to the test matrix job (least privilege).

🤖 Generated with Claude Code


Summary by cubic

Initial public release of pipelex-sdk (pipelex_sdk), the Python client for the Pipelex hosted API, reaching feature parity with @pipelex/sdk. Merging to main builds and publishes to PyPI via Trusted Publishing and creates a Sigstore-signed GitHub release.

  • New Features

    • PipelexAPIClient on mthds: Pipelex-branded env resolution (supports anonymous with api_token=""), host-only base URL validation, transport/error mapping (ApiResponseError, ApiUnreachableError), and health() at the origin.
    • Durable run lifecycle: get_run_status/get_run_result/wait_for_result, plus start_and_wait that falls back to blocking POST /v1/execute on bare runners. Adds RunFailedError, RunTimeoutError, RunLifecycleUnavailableError.
    • Product APIs: typed models and methods for user profile, methods CRUD, organizations, billing, Pipelex API keys, gateway inference key, onboarding, storage, and run records.
    • Validation: Pipelex-branded validation models and union; validate injects render of markdown and accepts mthds_sources; validate_files helper for file inputs.
    • Execute timeout handling: translates hosted ~30s gateway timeouts into PipelineExecuteTimeoutError with guidance to use start+poll.
    • Version: pipelex_sdk.__version__ sourced from installed metadata.
    • CI/CD: added PR gates (lint, tests, package, changelog, version, branch guards, CLA) and publish pipeline to PyPI with a signed GitHub release.
    • Compat: Python 3.10–3.14; depends on mthds>=0.6.1.
  • Bug Fixes

    • Respect api_token="" as anonymous even if other credentials exist (checks for None, not truthiness).
    • guard-branches.yml checks out the PR head and gates with a maintainer allow-list for fork safety.
    • tests-check.yml drops unnecessary id-token: write permission for least privilege.

Written for commit 68e9cda. Summary will update on new commits.

Review in cubic

lchoquel and others added 15 commits June 30, 2026 02:00
Greenfield scaffold for the Python client of the Pipelex hosted API,
mirroring mthds-python's tooling so every later phase runs on a green gate.

- pyproject.toml: PyPI `pipelex-sdk` / import `pipelex_sdk`, requires-python
  >=3.10,<3.15, hatchling, runtime deps (mthds>=0.5.0, pydantic, httpx,
  typing-extensions, backports.strenum) and the full ruff/pyright/mypy/
  pylint/pytest config. [tool.uv.sources] resolves mthds from ../mthds-python
  for dev; the published wheel depends on mthds>=0.5.0 from PyPI.
- Makefile: install/lock/build/test/agent-test/format/lint/pyright/mypy/
  pylint and the c/cc/check/agent-check aggregates.
- pipelex_sdk/_compat.py: local StrEnum/Self shim (no cross-package private import).
- Boilerplate: README, CHANGELOG (Unreleased), .gitignore, CLAUDE.md
  (overview + standards), docs/architecture.md skeleton, smoke test.

Kickoff decisions settled: package = pipelex-sdk/pipelex_sdk; client built by
inheritance (PipelexAPIClient(MthdsAPIClient)); credentials Pipelex-primary with
mthds fallback. Gate green: make install && make check && make agent-test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013cPeza9ezw38JFCi3uXP4m
PipelexAPIClient(MthdsAPIClient) — Pipelex-branded construction plus the
transport/error layer the product and lifecycle phases build on.

- errors.py: ApiResponseError (RFC 9457 `code` discriminant + problem-details)
  and ApiUnreachableError, both deriving from the protocol-base
  PipelineRequestError.
- client.py: __init__ override (PIPELEX_API_KEY/URL → mthds fallback; token
  optional/anonymous; host-only base-URL validation; origin_url for /health),
  start_client override (omit the Authorization header when anonymous), and the
  transport helpers _send_or_unreachable (httpx transport → ApiUnreachableError),
  _request_product (typed ApiResponseError, empty-body tolerant, PUT/PATCH/DELETE),
  _request_json (plainer regime), and the _parse_error_body problem+json parser.
- Tests (test-first style): construction (env precedence, optional token,
  base-URL validation), _parse_error_body across body shapes, _request_product
  (2xx/empty-body/non-2xx code mapping/transport), _request_json regime.
- Config: allow SLF001/PLC2701 in tests (probe internal helpers) and a
  pyright executionEnvironment disabling reportPrivateUsage under tests/.

Gate green: make check (ruff, pyright 0 errors, mypy, pylint 10.00/10) + make agent-test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013cPeza9ezw38JFCi3uXP4m
Lock in the repo convention (preferred over pipelex's per-line inline ignores):
test-only lint/type exemptions are declared once, scoped to tests/** in config
(ruff per-file-ignores + pyright executionEnvironments), never sprinkled inline.
The pyproject config already follows this; this just documents it so later
phases don't drift back to per-line ignores.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013cPeza9ezw38JFCi3uXP4m
Own the hosted run lifecycle in pipelex-sdk (ported from mthds-python and
enhanced), independent of mthds-python's state — a transient duplication
resolved by the Phase 6 strip.

- pipelex_sdk/runs.py: owned lifecycle models (RunStatus with is_terminal/
  is_success, RunRead, RunResults, the discriminated RunResultState union,
  WaitForResultOptions, PollInfo). RunResults names the Pipelex hosted artifacts
  (main_stuff, graph_spec, pipe_output) per the JS behavior spec.
- Lifecycle methods on PipelexAPIClient: get_run_status, get_run_result
  (202/503 -> running, 200 -> completed, 409 -> failed), wait_for_result. Poll
  GETs go through _send_or_unreachable, so transport failures surface as
  ApiUnreachableError and missing-route 404s as RunLifecycleUnavailableError.
- start override: translates a bare-runner missing-route 404 into a typed
  RunLifecycleUnavailableError (before any run is created), matching the JS SDK.
- start_and_wait self-heals hosted<->bare: cached GET /v1/version handshake
  (_supports_run_lifecycle) picks durable start+poll on hosted, falls back to
  the blocking POST /v1/execute (_execute_blocking) on a bare runner. Closes a
  gap vs mthds-python (whose start_and_wait raises on a bare runner).
- Lifecycle errors RunFailedError/RunTimeoutError/RunLifecycleUnavailableError
  owned here; RunStillRunningError re-exported from mthds (protocol 202-degrade).
- Tests: lifecycle status mapping, start override, poll/timeout/cancel, plus new
  coverage for the version-handshake caching and the bare-runner self-heal.
- Docs + CHANGELOG updated.

The owned lifecycle methods transiently shadow the base's (until Phase 6 strips
them from mthds-python); their owned return types read as incompatible overrides,
hence a narrow # type: ignore[override] that becomes harmless after the strip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013cPeza9ezw38JFCi3uXP4m
Add the hosted product routes as typed Pydantic v2 models
(pipelex_sdk/product_models.py) and client methods via _request_product:
user profile, methods CRUD, organizations, billing, Pipelex API keys,
gateway inference key, onboarding, storage, and run records. Documented
409-conflict behaviors surface through ApiResponseError.code (conflict;
pipelex_api_key_limit_reached).

Override validate to inject render: ["markdown"] (so valid + invalid
verdicts both carry rendered_markdown), accept a parallel mthds_sources
array, and add validate_files (synthesizes deterministic inline:// source
labels when any file carries a URI). The override delegates the wire call
to the inherited base validate, keeping body-building and the protocol
error regime shared.

Tests port pipelex-sdk-js/tests/product.test.ts (verb+path+body per route,
model round-trips with complete bodies, .code branching) plus validate
override coverage. Gate green: ruff, pyright, mypy, pylint 10.00/10, tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkphvaUvNJi6G613jq1q7j
Add `PipelexAPIClient.health()` — `GET {origin}/health`, served at the
origin (NOT under the `/v1` prefix) and out-of-protocol. Rides the plainer
`_request_json` regime (`PipelineRequestError` on a non-2xx,
`ApiUnreachableError` on transport failure), not the product
`ApiResponseError`. Mirrors `pipelex-sdk-js` `client.ts` `health()`.

Tests assert the origin-level path (outside `/v1`), the return shape, the
plainer error regime, and transport-failure mapping. Docs + CHANGELOG updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkphvaUvNJi6G613jq1q7j
…sion guard, release docs

Phase 5 of the pipelex-sdk-python build: the HANDOFF parity gate against the
TypeScript @pipelex/sdk reference. Surface-complete with no silent gaps.

Resolved the three deferred parity flags:
- execute() override + PipelineExecuteTimeoutError (Phase-1 flag) — IMPLEMENTED.
  A blocking POST /v1/execute killed by the hosted gateway's ~30s synchronous
  ceiling (503/504, or a client-side request timeout, observed at/after ~28s)
  is translated into a clear PipelineExecuteTimeoutError pointing at start+poll,
  closing the one genuine JS feature gap. Other non-2xx keep the inherited
  httpx.HTTPStatusError regime; 202 async-degrade still raises RunStillRunningError.
- health error regime (#5) — KEPT the plainer PipelineRequestError regime
  (already matches JS; liveness needs no code taxonomy).
- validate error regime (Phase-3 flag) — DEFERRED; keeps the inherited
  httpx.HTTPStatusError regime, consistent with the other Python protocol routes.

Also:
- __version__ (pipelex_sdk/version.py) derived from installed distribution
  metadata via importlib.metadata, with a test asserting it matches pyproject.
- Parity audit recorded in docs/architecture.md ("Parity with @pipelex/sdk"):
  method/model/error coverage, deliberate idiomatic ports, conscious exclusions
  (/v1/build/* per #8, WorkOS org-switch), and ClientAuthenticationError as a
  dormant non-port.
- README rewritten: quickstart (validate → start_and_wait → main_stuff/native),
  an err.code product example, and the full no-barrel import paths.
- CHANGELOG 0.1.0 entry.

Tests for the gateway-timeout translation (503/504/client-timeout past ceiling →
translated; fast 503 → untouched; success/202 passthrough) and the version guard.
Gate green: ruff, pyright 0, mypy clean, pylint 10.00/10, all tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkphvaUvNJi6G613jq1q7j
… floor to 0.6.0

mthds 0.6.0 is now protocol-only, so the owned lifecycle methods
(get_run_status / get_run_result / wait_for_result / start_and_wait) and
_raise_if_lifecycle_unavailable no longer shadow base methods — remove their
@OverRide decorators and # type: ignore[override] suppressions, which were
transition artifacts. @OverRide stays on start_client / execute / start /
validate (permanent protocol-route overrides; validate keeps its permanent
# type: ignore[override] since its signature genuinely differs).

Bump the mthds floor to >=0.6.0 (the protocol-only base the override cleanup
type-checks against).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkphvaUvNJi6G613jq1q7j
Move the Pipelex-branded validation models into the SDK
(pipelex_sdk/validation_models.py): PipelexValidationReport, PipelexInvalidReport,
the PipelexValidationResult union + adapter, and the supporting ValidationErrorItem
/ ValidationErrorCategory / ValidatedPipeEntry / DryRunStatus — narrowing mthds's
neutral verdict bases (mthds.protocol.models). PipelexAPIClient.validate() now parses
the 200 body into PipelexValidationResult via the inherited _post_validate transport
seam. Adds a local empty_list_factory_of util (pipelex_sdk/_pydantic_utils.py) and
moves the validation-contract round-trip test here.

Bumps the mthds floor to >=0.7.0 (the protocol-only-validate base). Completes the
brand-layering follow-up #9, fully mirroring the documented pipelex-sdk-js boundary;
the brand-neutral Dict* wire concretes stay in mthds and are reused by inheritance.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkphvaUvNJi6G613jq1q7j
Mirror the mthds-python CI suite, adapted to the Pipelex org and the
pipelex-sdk PyPI distribution:

- PR gates across the Python 3.10-3.14 matrix: lint-check, tests-check,
  package-check, changelog-check, version-check, guard-branches, cla
- publish.yml: build -> PyPI Trusted Publishing -> Sigstore-signed
  GitHub Release on push to main
- Add root CLA.md (Pipelex/Evotis CLA) and docs/ci-cd.md
- Align CHANGELOG.md to the `## [vX.Y.Z]` header convention the
  changelog/publish workflows key off

Also pin the published mthds floor to >=0.6.0, dropping the local
editable [tool.uv.sources] so the wheel resolves mthds from PyPI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rNPx1LVeHFCJWXPkicEWy
- client.py: honor explicit `api_token=""` as anonymous by testing
  `is not None` rather than truthiness, so the first present credential
  layer wins. Restores the documented "empty = anonymous" contract and
  JS SDK `??` parity (codex P2). Adds regression tests.
- guard-branches.yml: check out the PR head SHA so the workflow-protection
  diff actually sees fork edits, and gate on a maintainer allow-list so
  FIRST_TIME_CONTRIBUTOR / FIRST_TIMER / NONE authors are covered
  (codex + greptile P1).
- tests-check.yml: drop unused `id-token: write` from the untrusted PR
  test job (greptile P1, least privilege).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rNPx1LVeHFCJWXPkicEWy

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 68e9cda68b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

# diff below would compare base-against-base and never see the fork's changes. We
# only fetch/diff/grep here — the untrusted head is never executed — so checking
# out the PR head SHA is safe.
ref: ${{ github.event.pull_request.head.sha }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid checking out fork heads in pull_request_target

For external PRs this job runs under pull_request_target and checks out ${{ github.event.pull_request.head.sha }}; GitHub has announced that actions/checkout will refuse this exact pattern for fork PRs in pull_request_target workflows when the protection is backported to floating major tags like actions/checkout@v4 on July 16, 2026. Because this guard job only runs for non-members, it will start failing before the diff step and block all forked external PRs, not just workflow edits; use an API/list-files approach or another safe diff mechanism instead of checking out the fork head here.

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented Jul 1, 2026

Copy link
Copy Markdown

Greptile Summary

This PR publishes the first Python Pipelex SDK release. The main changes are:

  • New async pipelex_sdk client built on mthds.
  • Run lifecycle, product, validation, and error models.
  • Unit tests for construction, transport, lifecycle, product, validation, and version behavior.
  • Packaging, docs, changelog, Makefile tooling, and release workflows.

Confidence Score: 3/5

Release automation needs attention before merge because CI and publishing paths can execute unsafe installation logic or produce incorrect release artifacts.

The SDK implementation is broadly covered by unit tests, but the automation issues affect release integrity and CI safety.

Makefile and .github/workflows/publish.yml

T-Rex T-Rex Logs

What T-Rex did

  • The remote installer execution was reproduced in CI by running the harness with uv absent and curl/sh mocked, causing make check-uv and make lint to invoke the mocked installer pipeline.
  • A local GITHUB_ENV harness demonstrated a delimiter collision by injecting CHANGELOG_NOTES with an EOF marker, causing the parser to stop at EOF and leave invalid lines.
  • The base state showed a transition from initial build failure due to missing Python metadata to a successful wheel build and installation of pipelex-sdk 0.1.0 with proper metadata and version resolution.
  • The exercise captured in head showed HTTP contract paths producing full status outputs, including 200 OK and 409 Conflict, via the mocked transport.
  • The base checkout and runtime imports surfaced ModuleNotFoundError for pipelex_sdk, while the after run used mocked HTTP interactions and completed with exit code 0 and no failing contracts.
  • GitHub Actions contract checks passed after changes, and the assertion script was saved for later review.

View all artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
Makefile:102
**Remote Installer Runs In CI**

When a PR runner does not already have `uv`, the lint path reaches `make check-uv` and executes the live `astral.sh` installer through `sh`. That gives remote, unpinned script content the workflow environment and token for the job; install `uv` through a pinned action or verified artifact instead of piping the network response into a shell.

### Issue 2 of 3
.github/workflows/publish.yml:122-124
**Changelog Delimiter Corrupts Environment**

A release note that contains a line exactly equal to `EOF` closes this fixed `$GITHUB_ENV` heredoc before the changelog is fully written. The remaining markdown is then parsed as environment-file content, so the release job can fail or publish truncated notes; use a delimiter that cannot collide with changelog text.

### Issue 3 of 3
.github/workflows/publish.yml:75
**Release Version Uses First Match**

This grep reads the first `version = ` text anywhere in `pyproject.toml`, not the `[project]` version. If tool configuration with a version-like key is added above `[project]`, the wheel still builds with the correct metadata but the GitHub tag, release title, and changelog lookup use the wrong value.

Reviews (1): Last reviewed commit: "Release v0.1.0" | Re-trigger Greptile

@greptile-apps

greptile-apps Bot commented Jul 1, 2026

Copy link
Copy Markdown

T-Rex pricing update — T-Rex was free through June 2026. Effective July 1, 2026, T-Rex adds 2 credits on top of the standard 1-credit review (3 total). T-Rex settings

Comment thread Makefile
echo ""; \
echo "=== [$(PROJECT_NAME)] ===== (check-uv) ====== Ensuring uv ≥ $(UV_MIN_VERSION) =========="; \
echo "uv not found – installing latest …"; \
curl -LsSf https://astral.sh/uv/install.sh | sh; \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Remote Installer Runs In CI

When a PR runner does not already have uv, the lint path reaches make check-uv and executes the live astral.sh installer through sh. That gives remote, unpinned script content the workflow environment and token for the job; install uv through a pinned action or verified artifact instead of piping the network response into a shell.

Artifacts

Repro: harness that hides uv and safely mocks curl and sh while running make check-uv and make lint

  • Contains supporting evidence from the run (text/x-shellscript; charset=utf-8).

Repro: verbose output showing uv absent and the remote installer command invoked from check-uv and lint

  • Keeps the command output available without making the summary code-heavy.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: Makefile
Line: 102

Comment:
**Remote Installer Runs In CI**

When a PR runner does not already have `uv`, the lint path reaches `make check-uv` and executes the live `astral.sh` installer through `sh`. That gives remote, unpinned script content the workflow environment and token for the job; install `uv` through a pinned action or verified artifact instead of piping the network response into a shell.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +122 to +124
echo "CHANGELOG_NOTES<<EOF" >> $GITHUB_ENV
echo "$CHANGELOG_CONTENT" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Changelog Delimiter Corrupts Environment

A release note that contains a line exactly equal to EOF closes this fixed $GITHUB_ENV heredoc before the changelog is fully written. The remaining markdown is then parsed as environment-file content, so the release job can fail or publish truncated notes; use a delimiter that cannot collide with changelog text.

Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/publish.yml
Line: 122-124

Comment:
**Changelog Delimiter Corrupts Environment**

A release note that contains a line exactly equal to `EOF` closes this fixed `$GITHUB_ENV` heredoc before the changelog is fully written. The remaining markdown is then parsed as environment-file content, so the release job can fail or publish truncated notes; use a delimiter that cannot collide with changelog text.

How can I resolve this? If you propose a fix, please make it concise.

- name: Extract version and detect pre-release
id: get_version
run: |
VERSION=$(grep -m 1 'version = ' pyproject.toml | cut -d '"' -f 2)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Release Version Uses First Match

This grep reads the first version = text anywhere in pyproject.toml, not the [project] version. If tool configuration with a version-like key is added above [project], the wheel still builds with the correct metadata but the GitHub tag, release title, and changelog lookup use the wrong value.

Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/publish.yml
Line: 75

Comment:
**Release Version Uses First Match**

This grep reads the first `version = ` text anywhere in `pyproject.toml`, not the `[project]` version. If tool configuration with a version-like key is added above `[project]`, the wheel still builds with the correct metadata but the GitHub tag, release title, and changelog lookup use the wrong value.

How can I resolve this? If you propose a fix, please make it concise.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

9 issues found across 41 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".github/workflows/version-check.yml">

<violation number="1" location=".github/workflows/version-check.yml:36">
P1: `exit 0` in step "Get branch info" only exits that step's shell — it does not skip subsequent steps. Non-release PRs to `main` will always fail here because `source_release_version` is never set (empty), so the final comparison `"$PR_VERSION" != ""` evaluates to true and exits 1.</violation>
</file>

<file name=".github/workflows/cla.yml">

<violation number="1" location=".github/workflows/cla.yml:20">
P2: Pin third-party actions that receive secrets to full-length commit SHAs instead of mutable tags to prevent tag-retargeting or compromised-release takeover.</violation>
</file>

<file name=".github/workflows/publish.yml">

<violation number="1" location=".github/workflows/publish.yml:5">
P2: Publishing on every `main` push will make ordinary post-release merges attempt to republish the same package version. Gate this workflow on release tags or an explicit release trigger instead of all main pushes.</violation>

<violation number="2" location=".github/workflows/publish.yml:122">
P1: Use a unique delimiter when writing multiline changelog content to `$GITHUB_ENV`; a fixed `EOF` marker can terminate early if the notes contain a standalone `EOF` line.</violation>
</file>

<file name="pipelex_sdk/product_models.py">

<violation number="1" location="pipelex_sdk/product_models.py:61">
P2: Input payload models are not configured to forbid extras, so misspelled/unsupported request fields are silently dropped instead of failing validation. Add strict `model_config` to each request-body model.</violation>
</file>

<file name=".github/workflows/lint-check.yml">

<violation number="1" location=".github/workflows/lint-check.yml:3">
P2: Missing `push` trigger for main branch. Lint checks only run on PRs, not on pushes to main (e.g., merges). Add `push: branches: [main]` so the workflow enforces lint after merge commits too.</violation>

<violation number="2" location=".github/workflows/lint-check.yml:12">
P2: Missing least-privilege `permissions` block. A lint-only workflow needs `contents: read` at most. The inherited default grants write access to contents, issues, and pull-requests — unnecessary privilege for untrusted PR code.</violation>
</file>

<file name=".github/workflows/guard-branches.yml">

<violation number="1" location=".github/workflows/guard-branches.yml:68">
P2: Workflow-change detection uses a full tree diff instead of PR delta. This can falsely reject external PRs that did not modify workflows.</violation>
</file>

<file name="Makefile">

<violation number="1" location="Makefile:102">
P1: Avoid piping a remote installer directly into `sh` in this target. This path is used in CI, so unpinned network content executes inside the workflow environment.</violation>
</file>

You're on the cubic free plan with 19 free PR reviews remaining this month. Upgrade for unlimited reviews.
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

echo "Source is NOT a release branch - skipping version check"
echo "is_release_source=false" >> $GITHUB_OUTPUT
echo "This workflow only runs for PRs from release branches to main"
exit 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: exit 0 in step "Get branch info" only exits that step's shell — it does not skip subsequent steps. Non-release PRs to main will always fail here because source_release_version is never set (empty), so the final comparison "$PR_VERSION" != "" evaluates to true and exits 1.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/version-check.yml, line 36:

<comment>`exit 0` in step "Get branch info" only exits that step's shell — it does not skip subsequent steps. Non-release PRs to `main` will always fail here because `source_release_version` is never set (empty), so the final comparison `"$PR_VERSION" != ""` evaluates to true and exits 1.</comment>

<file context>
@@ -0,0 +1,74 @@
+            echo "Source is NOT a release branch - skipping version check"
+            echo "is_release_source=false" >> $GITHUB_OUTPUT
+            echo "This workflow only runs for PRs from release branches to main"
+            exit 0
+          fi
+          echo "======================================="
</file context>

Comment thread Makefile
echo ""; \
echo "=== [$(PROJECT_NAME)] ===== (check-uv) ====== Ensuring uv ≥ $(UV_MIN_VERSION) =========="; \
echo "uv not found – installing latest …"; \
curl -LsSf https://astral.sh/uv/install.sh | sh; \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Avoid piping a remote installer directly into sh in this target. This path is used in CI, so unpinned network content executes inside the workflow environment.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Makefile, line 102:

<comment>Avoid piping a remote installer directly into `sh` in this target. This path is used in CI, so unpinned network content executes inside the workflow environment.</comment>

<file context>
@@ -0,0 +1,309 @@
+		echo ""; \
+		echo "=== [$(PROJECT_NAME)] ===== (check-uv) ====== Ensuring uv ≥ $(UV_MIN_VERSION) =========="; \
+		echo "uv not found – installing latest …"; \
+		curl -LsSf https://astral.sh/uv/install.sh | sh; \
+	}
+	@uv self update >/dev/null 2>&1 || true
</file context>

CHANGELOG_CONTENT=$(printf "%s\n\n%s" "$HEADER_LINE" "$CONTENT_LINES")

# Escape for GitHub Actions
echo "CHANGELOG_NOTES<<EOF" >> $GITHUB_ENV

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Use a unique delimiter when writing multiline changelog content to $GITHUB_ENV; a fixed EOF marker can terminate early if the notes contain a standalone EOF line.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/publish.yml, line 122:

<comment>Use a unique delimiter when writing multiline changelog content to `$GITHUB_ENV`; a fixed `EOF` marker can terminate early if the notes contain a standalone `EOF` line.</comment>

<file context>
@@ -0,0 +1,172 @@
+          CHANGELOG_CONTENT=$(printf "%s\n\n%s" "$HEADER_LINE" "$CONTENT_LINES")
+
+          # Escape for GitHub Actions
+          echo "CHANGELOG_NOTES<<EOF" >> $GITHUB_ENV
+          echo "$CHANGELOG_CONTENT" >> $GITHUB_ENV
+          echo "EOF" >> $GITHUB_ENV
</file context>

Comment thread .github/workflows/cla.yml
@@ -0,0 +1,42 @@
name: "CLA Assistant bot"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Pin third-party actions that receive secrets to full-length commit SHAs instead of mutable tags to prevent tag-retargeting or compromised-release takeover.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/cla.yml, line 20:

<comment>Pin third-party actions that receive secrets to full-length commit SHAs instead of mutable tags to prevent tag-retargeting or compromised-release takeover.</comment>

<file context>
@@ -0,0 +1,42 @@
+    steps:
+      - name: Get GitHub App token
+        id: app-token
+        uses: actions/create-github-app-token@v3
+        with:
+          app-id: ${{ secrets.CLA_GH_APP_ID }}
</file context>


on:
push:
branches:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Publishing on every main push will make ordinary post-release merges attempt to republish the same package version. Gate this workflow on release tags or an explicit release trigger instead of all main pushes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/publish.yml, line 5:

<comment>Publishing on every `main` push will make ordinary post-release merges attempt to republish the same package version. Gate this workflow on release tags or an explicit release trigger instead of all main pushes.</comment>

<file context>
@@ -0,0 +1,172 @@
+
+on:
+  push:
+    branches:
+      - main
+
</file context>

@@ -0,0 +1,360 @@
"""Pipelex-product wire models — the snake_case JSON shapes the hosted-product routes speak.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Input payload models are not configured to forbid extras, so misspelled/unsupported request fields are silently dropped instead of failing validation. Add strict model_config to each request-body model.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pipelex_sdk/product_models.py, line 61:

<comment>Input payload models are not configured to forbid extras, so misspelled/unsupported request fields are silently dropped instead of failing validation. Add strict `model_config` to each request-body model.</comment>

<file context>
@@ -0,0 +1,360 @@
+    updated_at: str
+
+
+class MethodWriteInput(BaseModel):
+    """The create/update payload — a rename is a `PUT` with a changed `name`."""
+
</file context>

@@ -0,0 +1,65 @@
name: Lint check

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Missing least-privilege permissions block. A lint-only workflow needs contents: read at most. The inherited default grants write access to contents, issues, and pull-requests — unnecessary privilege for untrusted PR code.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/lint-check.yml, line 12:

<comment>Missing least-privilege `permissions` block. A lint-only workflow needs `contents: read` at most. The inherited default grants write access to contents, issues, and pull-requests — unnecessary privilege for untrusted PR code.</comment>

<file context>
@@ -0,0 +1,65 @@
+# --------------------------------------------------------------------------
+  lint:
+    name: Lint (${{ matrix.python-version }})
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
</file context>

@@ -0,0 +1,65 @@
name: Lint check

on:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Missing push trigger for main branch. Lint checks only run on PRs, not on pushes to main (e.g., merges). Add push: branches: [main] so the workflow enforces lint after merge commits too.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/lint-check.yml, line 3:

<comment>Missing `push` trigger for main branch. Lint checks only run on PRs, not on pushes to main (e.g., merges). Add `push: branches: [main]` so the workflow enforces lint after merge commits too.</comment>

<file context>
@@ -0,0 +1,65 @@
+name: Lint check
+
+on:
+  pull_request:
+
</file context>

fetch-depth: 0
- name: Detect workflow changes
run: |
git fetch origin "${{ github.event.pull_request.base.ref }}" --depth=1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Workflow-change detection uses a full tree diff instead of PR delta. This can falsely reject external PRs that did not modify workflows.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/guard-branches.yml, line 68:

<comment>Workflow-change detection uses a full tree diff instead of PR delta. This can falsely reject external PRs that did not modify workflows.</comment>

<file context>
@@ -0,0 +1,73 @@
+          fetch-depth: 0
+      - name: Detect workflow changes
+        run: |
+          git fetch origin "${{ github.event.pull_request.base.ref }}" --depth=1
+          CHANGED=$(git diff --name-only FETCH_HEAD HEAD | grep -E '^\.github/workflows/.*\.ya?ml$' || true)
+          if [ -n "$CHANGED" ]; then
</file context>

Gate workflow-edit trust on the author's effective repository permission
(resolved via getCollaboratorPermissionLevel, only write/admin trusted,
failing closed on non-404 API errors) instead of the spoofable
author_association label, and drop to least-privilege contents: read.
Mirrors mthds-python.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rNPx1LVeHFCJWXPkicEWy
@lchoquel
lchoquel merged commit 844f7ce into main Jul 1, 2026
15 checks passed
@lchoquel
lchoquel deleted the release/v0.1.0 branch July 1, 2026 07:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant