Skip to content

fix(org-deploy): move the marker with curl, not gh - #28

Merged
gambe94 merged 1 commit into
mainfrom
fix/marker-without-gh-cli
Aug 13, 2026
Merged

gambe94 merged 1 commit into
mainfrom
fix/marker-without-gh-cli

Conversation

@gambe94

@gambe94 gambe94 commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

sf-ci has no gh CLI. curl keeps the single-round-trip win.

The simplification pass replaced a full bare clone with `gh api` — one
ref update instead of downloading the whole history. Correct instinct,
wrong tool: the sf-ci container has no GitHub CLI, so the step died with
`gh: command not found` after a successful deployment.

curl is in the image, so the round-trip saving survives. PATCH first and
treat 422/404 as "create it", rather than probing for existence and
paying a second call.

Also gives the failure a consequence the reader can act on: the deploy
succeeded, so the next delta recomputes from the old base and redeploys
these components — noisy, not dangerous.

Caught by running the pipeline; the checksum script, the single-traversal
digest, the relocated env-config export and the template-count invariant
all passed in the same run.
@gambe94
gambe94 merged commit 020dc10 into main Aug 13, 2026
2 checks passed
@gambe94
gambe94 deleted the fix/marker-without-gh-cli branch August 13, 2026 23:03
gambe94 added a commit that referenced this pull request Aug 14, 2026
…30)

* Docs: point CLAUDE.md at docs/ and install the L2 AI layer

CLAUDE.md duplicated docs/pipeline-map.md and docs/architecture.md inline.
Replaced with summaries plus links; the docs were already the better source.
Verified every fact in the removed text against the two routed docs before
cutting it - nothing was dropped, it was already covered there.

Adds the gforce-github-actions skill (lockfile) and the gha-workflow-author
agent, and AGENTS.md as a symlink for cross-vendor coverage.

Also fixes two pre-existing defects in docs/ surfaced by reviewing the new
skill, since it now routes every session straight to these files:

- docs/consuming-sf-cicd.md still pointed at the frozen v1 release tag and
  told readers to copy examples/sf-pr-validate.yml and
  examples/sf-release.yml, filenames that do not exist post-rename (they are
  reusable-sf-pr-validate.yml and reusable-sf-release.yml now). Its sibling
  consuming-sf-dispatch.md was updated to v2 during the rename; this one was
  missed. Fixed both.
- docs/claude-actions-reference.md was missing entries for six assets added
  in PR #27 and PR #28: sf-artifact-build, sf-artifact-deploy,
  sf-env-config-apply, reusable-sf-org-deploy, smoke-sf-artifact, and
  smoke-sf-org-login. Added an entry for each, sourced from each asset's own
  action.yml / workflow file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Fix: correct false claim in 069f926 and four review findings

069f926's message claimed "nothing was dropped" when CLAUDE.md's TypeScript-
actions text was cut. That claim was inaccurate: the removed text named
ApexTestSelectionService and package.xml parsing as the substance of
libraries/salesforce, and neither appeared anywhere under docs/ -
architecture.md described that directory only generically. Restored the
detail to docs/architecture.md (the doc that owns code layering), not back
into CLAUDE.md, which is already near its 150-line budget.

Also:

- docs/consuming-sf-cicd.md: the @v1 fix in 069f926 was incomplete. Lines 9,
  18, 52 and 55 still used the pre-rename short names sf-pr-validate.yml /
  sf-release.yml while the real files, examples/README.md, and this doc's
  own link targets all use reusable-sf-pr-validate.yml /
  reusable-sf-release.yml - line 52 was self-contradictory within one link.
  Completed the rename throughout; re-read the file end to end afterward and
  found no remaining v1 references, no dead paths, and no contradiction with
  consuming-sf-dispatch.md.
- docs/claude-actions-reference.md: sf-artifact-build's head-commit entry
  claimed a default of GITHUB_SHA; the action.yml declares default: "" with
  the $GITHUB_SHA fallback applied in the script body at runtime. Corrected.
- docs/claude-actions-reference.md: the smoke-sf-artifact.yml entry called
  reject-validate-without-tests "a fifth job" while conflating it with the
  workflow's five-property header comment. The workflow has 4 jobs
  (build-artifact covers properties 1/3/4, unset-replacement-fails covers 2,
  reject-tampered-artifact covers 5); reject-validate-without-tests is the
  4th job, sitting outside the five-property enumeration. Reworded.
- docs/claude-actions-reference.md: the note flagging that sf-org-login
  predates PR #16's credential-source: github-env input lived only at the
  end of the smoke-sf-org-login.yml entry (~line 396), 280 lines from the
  stale sf-org-login entry itself (~line 115) where an editor would actually
  need it. Added a one-line pointer at the sf-org-login entry; left the
  existing note in place. The underlying staleness is still out of scope.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: name the actual token tooling, drop graphify

graphify was uninstalled on 2026-08-14; CLAUDE.md still told every session it
was available, and omitted rtk, which is the thing actually compressing Bash
output through a PreToolUse hook.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Add: github-app-token, a fail-closed replacement for cross-repo PATs

`secrets.GITHUB_TOKEN` cannot reach another repository, so every cross-repo job
in the fleet reached for a personal access token instead. Two are in service
today — `E2E_DISPATCH_TOKEN` in sf-docker-images and `CATALOG_SCAN_TOKEN` here.
A PAT is the worst credential available for the job: it belongs to a person, it
has account-wide scope, it never expires on its own, and it dies with the
account.

An App installation token is better on all four counts, but only if it is
actually scoped. `actions/create-github-app-token` is generous in two directions
at once — omit `repositories` and the token reaches every repo in the
installation; omit every `permission-*` and it inherits every permission the App
holds. Do both and the replacement for an over-broad PAT is an over-broad token
that merely expires sooner.

So this action refuses to mint until the caller has named the repositories AND
named the permissions. `allow-broad-scope: true` is the deliberate opt-out, and
it logs a warning saying so.

Also validated here, because each failure is otherwise diagnosed from a bare
401/404 in a later step:

  - neither client-id nor app-id set (an unshared org variable arrives as an
    empty string, indistinguishable from never passing one)
  - a private key without its PEM armour
  - an empty private key

`client-id` is preferred; upstream deprecated the numeric `app-id` in v3, and
both are accepted so a caller can migrate without a flag day.

Three CI smoke steps pin the guards. They run with a throwaway key and no App
credentials at all, which is the point — the two scope rejections would
otherwise SUCCEED and hand back a working, over-broad token. Verified locally
across 11 input combinations before wiring: every reject case rejects, every
scoped case passes, and `allow-broad-scope` still opens the door.

Registered with Dependabot per the authoring convention — the action carries a
third-party `uses:`, and composite directories are not covered by "/".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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