Skip to content

feat(workflows): host the reusable GitHub workflows in this repo - #120

Merged
metalwarrior665 merged 13 commits into
masterfrom
claude/repo-merge-actions-package-ep3lzw
Sep 22, 2026
Merged

metalwarrior665 merged 13 commits into
masterfrom
claude/repo-merge-actions-package-ep3lzw

Conversation

@metalwarrior665

Copy link
Copy Markdown
Member

This PR introduces a complete set of reusable GitHub workflows and supporting infrastructure that consumer repositories can use for testing, building, and releasing their Actors.

Summary

The package now includes production-ready reusable workflows alongside the CLI, enabling consumer repos to standardize their CI/CD pipelines. These workflows handle platform testing, PR validation, releases, and Claude-powered automated investigation and fixes for failing tests.

Key Changes

  • Reusable Workflows (.github/workflows/):

    • pr-build-test.yaml: Runs unit tests and platform tests on pull requests
    • platform-tests.yaml: Scheduled platform test runs with Slack reporting
    • push-build-latest.yaml: Builds and releases Actors on master push
    • claude.yaml: Responds to @claude mentions to implement fixes via Claude Code
    • platform-tests-claude-investigate-and-fix.yaml: Two-phase workflow that uses Claude to investigate failing tests and automatically open issues and PRs
    • _move_major_tag.yaml: Manages the floating @v1 tag, only moving it when the required package version is published
    • manual_move_major_tag.yaml: Escape hatch for manual tag management
  • GitHub Actions (.github/actions/):

    • checkout-restore-dependencies/action.yaml: Composite action for checkout, Node setup, and dependency caching with npm token isolation
  • Helper Scripts (.github/scripts/):

    • run-with-apify-tokens.mjs: Securely passes only required Actor tokens to CLI commands, preventing secret leakage to node_modules
  • Configuration:

    • .github/workflows-min-package-version: Declares the minimum CLI version required by the workflows, gating tag movement until that version is published
  • Documentation:

    • Updated README.md with workflow usage examples, secret handling details, and versioning guidance
    • Updated CONTRIBUTING.md to document the workflows as a third component of the package

Notable Implementation Details

  • Secret Isolation: Secrets are passed only to the steps that need them, never as job-wide environment variables. The run-with-apify-tokens.mjs script reads apify-test-tools.config.json to determine which Actor tokens to pass, preventing accidental exposure.

  • Versioning Strategy: Consumer repos pin workflows to @v1 (a floating major tag), not @master. The tag only moves when the declared minimum package version is published on npm, preventing workflows from calling unreleased CLI features.

  • Claude Integration: The platform-tests-claude-investigate-and-fix.yaml workflow chains investigation and fix phases as job dependencies rather than label triggers, working around GitHub's limitation that scheduled runs cannot trigger other workflows.

  • Backward Compatibility: The platform-tests.yaml workflow supports both the deprecated subtest input and the new test-files-glob input for gradual migration.

https://claude.ai/code/session_01SkUZADZ6gzWW4GE6CFMreM

Moves the contents of apify-store/github-actions-source here, so a change that
spans a CLI feature and the workflow calling it is one PR against one branch
instead of two repos with a manual ref dance between them.

Consumers pin the `@v1` major tag rather than `@master`, and the setup action
installs `apify-test-tools@>=<floor>` (from .github/workflows-min-package-version)
instead of `@latest`. That resolves to the same newest stable in the normal case,
but makes a workflow's package requirement explicit.

The release cadences stay independent, because most changes only touch one side:

- workflow-only change: merge, `v1` moves, live, no npm release
- package-only change: merge and release when ready, workflows untouched
- workflow calling a new CLI feature: raise the floor in the same PR. On merge the
  `v1` move is held with a warning until that version is on npm, so consumers keep
  running the previous workflows instead of calling a CLI that doesn't exist yet.
  A stable release moves the tag; `manual_move_major_tag` is the escape hatch.

Also:
- run-with-apify-tokens.mjs moves to .github/scripts/, next to the other CI helper
  and out of eslint's path; the action's `scripts-path` output follows it
- pass github.head_ref/base_ref through the environment in pr-build-test, since
  branch names are attacker-controlled on fork PRs and actionlint gates master here
- prettier formatting on the imported files, which husky enforces here

Refs in the consumer snippets in the README were still `@new_master` and are
updated along with the repo path.
@metalwarrior665 metalwarrior665 changed the title Add reusable GitHub workflows and Claude integration feat(workflows): host the reusable GitHub workflows in this repo Aug 20, 2026
actionlint gates master here and the action wrapper runs shellcheck, which the
old repo's CI did not, so this SC2086 came in with the import. Unquoted it would
word-split if the runner ever pointed $GITHUB_OUTPUT at a path with spaces.
claude and others added 6 commits September 10, 2026 13:15
Catches the merged copy up with the four commits that landed on
github-actions-source master after the import (#57, #59, #60, #63), all of which
built the claude-review action. Two new files, nothing else changed upstream.

`review.yaml` fetches its instructions over HTTP rather than from a checkout,
because a reusable workflow runs with the caller's repo checked out and never
gets its own, so the URL had to follow the move to this repo. Its `prompt-ref`
default moves from `master` to `v1`: consumers call the workflow at `@v1`, and
defaulting the prompt to master would run released workflows against unreleased
instructions, which is the skew the tag gate exists to prevent.

Also drops `RUN_PLATFORM_TESTS` from pr-build-test. Master removed it in #122 in
favour of gating on `TESTER_APIFY_TOKEN`, which that step already sets, so after
merging master the variable was config nothing reads.

Two notes on the checks:

- .github/actionlint.yaml ignores two errors on review.yaml. actionlint bakes in
  a snapshot of popular actions' interfaces from when the pinned version shipped,
  and claude-code-action has grown since, so it flags `display_report` and the
  `conclusion` output as undefined. Both are declared in the action's action.yml
  at @v1 — verified before suppressing, and the patterns name those two symbols
  so unrelated bad inputs and outputs in that file still fail.
- .github/review-prompt.md is prettier-ignored. Prettier collapses the nested
  bullet list under "do not visit, fetch, infer, or evaluate the following
  external links" into one run-on line, changing what the model is told. Keeping
  it byte-identical also makes re-syncing it a plain copy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SkUZADZ6gzWW4GE6CFMreM
… the refs

Cuts the major tag as `v0` rather than `v1`, since the package is on 0.x and will
keep releasing 0.x for a while. Nothing in the gate logic reads the number, so
this is only the tag name and the refs that repeat it.

Keeping consumers on `@master` until the package hits 1.0 was the alternative,
and it reopens the gap this was built to close: the floor gate has teeth only
because consumers track a tag that can be held. On `@master` a workflow calling
an unreleased CLI feature ships on merge and fails in every consumer's CI,
instead of simply not shipping.

The tag tracks the workflows' contract, not the package version — they move
independently on purpose, so `v0` is expected to outlive the package reaching
1.0. Bump it when a workflow breaks its callers, not when the package does.

Adds check-major-tag-refs.mjs to _check_code, because `uses:` cannot take an
expression, so the tag is repeated in all seven refs into this repo plus the
README snippets people copy. Missing one during a bump is silent in the case
that matters: right after a bump both tags exist, so workflows called at the new
tag pull the composite action from the old one and run a stale version of it
without failing. The check compares every self-reference and review.yaml's
prompt-ref default against MAJOR_TAG and names the file and line that disagree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SkUZADZ6gzWW4GE6CFMreM
GitHub only reads workflow files at the top level of .github/workflows, so the
workflows other repos call sit in the same directory as this repo's own CI with
nothing distinguishing them. The prefix makes the boundary readable: `public_` is
the API other repos depend on, `_` is internal plumbing, `on_`/`manual_` are this
repo's own triggers.

Renamed: public_pr-build-test, public_platform-tests, public_push-build-latest,
public_claude, public_review, public_platform-tests-claude-investigate-and-fix.

Breaking for anyone already calling these by path, which today is nobody: the v0
tag does not exist yet and consumers still point at the old repo. Doing it now
costs nothing; after the first tag it would cost a major bump, since a filename
is baked into every consumer's `uses:` line. CONTRIBUTING says so where the
convention is introduced.

Two references would have broken quietly rather than loudly:

- .github/actionlint.yaml keys its ignores by path, so a stale key silently stops
  suppressing and the claude-code-action false positives come back. Verified by
  pointing the key at the old name and watching both errors return.
- check-major-tag-refs.mjs reads review.yaml by path to check its prompt-ref
  default; a stale path would have downgraded that to a soft "could not read".

The separators are now mixed (public_platform-tests), since the prefix is
underscore-style and the imported names are hyphenated. Left as-is because the
rest of the filename is the part consumers type.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SkUZADZ6gzWW4GE6CFMreM
…t a floor bump

The floor is a hand-written declaration, so the tag hold only protects consumers
if someone remembers to raise it. Forgetting is silent at merge time and loud
everywhere else: the tag moves, the workflow goes live, and it calls a CLI that
is not on npm, breaking every consumer's CI at once.

The check fires only when a single PR touches both sides — a `public_` workflow
or the composite action, and `bin/`, `lib/` or `index.ts` — and no floor bump.
`lib/` counts because the platform tests import it, so a workflow can depend on
its behaviour as much as on a CLI flag.

Deliberately scoped to one PR. A workflow could also start using something from
an earlier unreleased PR, which this will not see. Catching that means comparing
against the floor's release tag, which flags every workflow edit made while any
package change sits unreleased — far more noise than a case that needs someone to
land a CLI change and then sit on it.

Unrelated changes do land in one PR, so `no-floor-bump-needed` skips the check
rather than leaving no way past it. The failure message names both halves of the
diff and spells out both exits.

Verified against ten path combinations, including the real diff of this branch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SkUZADZ6gzWW4GE6CFMreM
Syncs github-actions-source#62, the one commit that landed there since the last
sync. Gives the investigate phase read-only access to the failing run through the
Apify MCP server, so it can read the run, its log and its storages instead of
working from workflow logs alone, and pins claude-code-action to a SHA.

Applied to the renamed public_ file; the patch landed unchanged otherwise.

Documents the new TESTER_APIFY_TOKEN_READ_ONLY in the README secrets table, since
it is consumer-facing: the workflow declares it `required: true`, so a repo
calling this workflow without that secret set will fail the call. Upstream has it
on master already, which is live, so this is not new breakage — but repos that
pick it up when they migrate to @v0 need the secret in place first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SkUZADZ6gzWW4GE6CFMreM
@metalwarrior665
metalwarrior665 marked this pull request as ready for review September 15, 2026 09:01

@ruocco-l ruocco-l left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It looks good to me, just a question on the v0->v1 migration, which maybe I don't quite fully understand 😅

Comment on lines +36 to +39
# Bump this only for a breaking change to a workflow's inputs, secrets or behaviour.
# The previous tag then stops moving and keeps working, so repos migrate when they get
# to it instead of all at once.
#

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not that is the most pressing thing, but migrating the tag from v0 to v1 is confusing for me. It's true that v0 freezes, but the actions will still install the latest version of the library (which will be allowed by the floor because it would set a lower version of the library for sure). That way we will probably have a library that works just for v1 and v0 breaks as soon as the newer version of the library is published.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch. Claude instead went with the exact version pin per action release tag. I think that also generally simplifies the mental model, each actions commit release will bound to extract library version.

277f959

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@artogahr and @JuanGalilea smuggled two commits in while you were doing this so this action is not 1:1 what we have in the other repo 👀

@metalwarrior665 metalwarrior665 Sep 18, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I will sync the new updates before merging. Will need to keep syncing before people move.

…floor

The workflows installed `apify-test-tools@>=<floor>`. That is open-ended, so it
resolved to whatever was newest on npm at run time, and a frozen major tag froze
the workflow files but not their dependency: `v0` would have kept pulling newer
library versions after `v1` was cut. Since the release that enables `v1` is
normally the same release that breaks `v0`, the migration window was zero, and
the README's claim that the old tag keeps working was false.

The file now holds one exact version and the workflows install exactly it, so a
tag is a complete statement — these workflows and this library — and a frozen tag
keeps what it was tested against however far the package moves on. Verified
against the registry: pin 0.8.0 installs 0.8.0 where `>=0.8.0` resolved to 0.9.0.

The stable release writes the pin into the commit that already bumps package.json
and CHANGELOG.md, so a released commit always names the version it published and
rollout latency is unchanged — the release publishes and moves the tag in one run.
`signed-commit` stages `.` by default, so the file rides along.

The write is guarded with `release_type != 'prerelease'`. _update_release_metadata
serves both paths and on_master passes `prerelease`, so writing unconditionally
would have pinned a `-beta` and shipped betas to every consumer. Betas stay
reachable through the lockfile path in the setup action, which is how branch
testing works.

Accepted trade-off: a standalone manual_publish_to_npm dispatch no longer reaches
consumers, because it does not write the pin. An unrecorded publish silently
changing what every consumer runs is the behaviour worth losing.

Renames follow the concept: check-floor-bump.mjs becomes
check-package-version-bump.mjs, its override label becomes
`no-version-bump-needed`, and the two PR jobs become `Package version bump
needed` and `Workflows package version`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SkUZADZ6gzWW4GE6CFMreM
@JuanGalilea

Copy link
Copy Markdown
Contributor

so the idea is for this repo to become all things CI/CD? since there are several unrelated actions 👀 (like claude stuff)

@metalwarrior665

Copy link
Copy Markdown
Member Author

so the idea is for this repo to become all things CI/CD?

The main reason is that the actions are closely tied to this repo and we need to deploy them in sync. Haing them here allows us to to have the plumbing for that. Also one less repo to maintain.

since there are several unrelated actions 👀 (like claude stuff)

It is called apify-test-tools so I can imagine it can have stuff that you just pick like a buffet. But I could imagine that we host some unrelated actions separately. For now it will be easier to have it in one repo though.

@JuanGalilea JuanGalilea left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can we move the actions not being used in this repo into a subfolder?
So at least we know which ones are used and which ones are "exports"?
I know the cost of this will be one more ./something when calling them but some order here would be benefitial

@JuanGalilea

JuanGalilea commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

btw, this will fuck up sync apify-store/github-actions-source#67 (its very short tho)

@metalwarrior665

Copy link
Copy Markdown
Member Author

Sadly,.Github seems to force workflows to be in the same top level folder so the only solution is a prefix.

metalwarrior665 and others added 4 commits September 22, 2026 11:09
Carries across everything that landed upstream since the last sync (9263146),
and merges apify-test-tools master, which had moved six commits including the
config rework and the pr-title-check bump to v1.6.1.

- #61 + #66 — run changed platform tests when the branch rebuilds no actor. Test
  files sit outside every actor's Docker build context, so a test-only PR used to
  pass in 25s having run nothing. #66 fixes #61: a changed helper passed to vitest
  as a plain filter matches no test file and exits 1, so it uses `related`.
- #65 — a tighter Claude issue-investigator prompt.
- #67 — off Node 20: setup-node v5 (with package-manager-cache disabled, since
  the node_modules cache below it is the one we want), checkout v5, upload-artifact v6.

#65 applied clean. The other three needed hand-application, since the local copies
have diverged — the composite action for the version pin, pr-build-test for the
injection fix and prettier.

Two upstream details deliberately not carried over:

- the new step interpolates github.head_ref and github.base_ref straight into
  `run:`. Branch names are attacker-controlled on fork PRs and actionlint gates
  master here, so they go through the environment like the other steps. The two
  `$GITHUB_OUTPUT`/`$GITHUB_STEP_SUMMARY` writes are quoted for the same reason.
- upstream still sets RUN_PLATFORM_TESTS, which master dropped in #122 in favour
  of gating on TESTER_APIFY_TOKEN. It stays dropped; RUN_ALL_PLATFORM_TESTS is
  new and is still read by lib, so that one is carried.

`[ "$work_dir" = "." ] && work_dir=""` looks like it would trip GitHub's `bash -e`
shell when the working directory is not `.`, but bash exempts a failing command
that is not the last in an AND list — checked before porting it unchanged.

The new step calls `get-affected-actors`, which the pinned 0.9.0 already has, so
no version bump. The `Package version bump needed` check stays quiet correctly:
this PR changes no bin/, lib/ or index.ts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SkUZADZ6gzWW4GE6CFMreM
@metalwarrior665
metalwarrior665 merged commit 10f089d into master Sep 22, 2026
12 checks passed
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.

5 participants