Dual-publish releases to public npm and the internal copilot-canary feed - #23
Conversation
Publishes @github/copilot-engine-sdk canary builds to the internal Azure Artifacts copilot-canary feed only, never public npm. Adds .github/workflows/canary-publish.yml with fork guard, feed-only publish guards, prerelease version scheme, and idempotent publish, plus README docs for consuming from the feed. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b1cc5615-75ab-477b-9dbd-feff9d63a426
There was a problem hiding this comment.
Pull request overview
Adds an internal-only Azure Artifacts canary publishing path and consumer setup instructions.
Changes:
- Adds manual canary build and publishing workflow.
- Implements feed authentication, versioning, and publish safeguards.
- Documents internal canary installation.
Show a summary per file
| File | Description |
|---|---|
.github/workflows/canary-publish.yml |
Builds and publishes canary packages. |
README.md |
Documents internal feed consumption. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 2/2 changed files
- Comments generated: 4
- Review effort level: Medium
Rework publish.yml into two jobs mirroring copilot-agent-runtime's publish-cli.yml: a publish job (public npm, environment npm-publish) that packs once and uploads the tarball artifact, and a publish-internal job (environment cicd) that publishes the identical tarball to the copilot-canary feed via OIDC with an explicit --registry guard. Port script/npm-publish-idempotent.sh for retry-safe publishes, drop the standalone canary workflow, add .gitattributes to keep the script LF, and reframe the README section as an internal-feed mirror. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b1cc5615-75ab-477b-9dbd-feff9d63a426
There was a problem hiding this comment.
Review details
Comments suppressed due to low confidence (3)
.github/workflows/publish.yml:117
- This does not make retries of the public job idempotent. A failed-job re-run starts from the beginning; if npm publication succeeded and the later GitHub release step failed,
npm viewnow sees that new version and the workflow increments it again, so the conflict handler is never exercised (and, once artifact re-upload is fixed, the retry publishes another irreversible version). Persist/reuse the originally selected version and tarball across attempts, or split preparation, npm publication, and GitHub release into dependent jobs so successful stages are not rerun.
# Idempotent so a re-run of a failed job doesn't fail on an already
# published version. Explicit --registry keeps the target unambiguous.
if bash script/npm-publish-idempotent.sh --run "$TARBALL" \
.github/workflows/publish.yml:107
upload-artifact@v4rejects a second upload with the same name in one workflow run by default. If this job is re-run after the artifact was uploaded but a later step failed, it will stop here with a 409 instead of reaching the idempotent publish logic. Allow replacement of the prior attempt's artifact.
This issue also appears on line 115 of the same file.
name: npm-tarball
path: ${{ steps.pack.outputs.tarball }}
if-no-files-found: error
retention-days: 7
README.md:40
- The “npm (above)” reference is incorrect: the preceding installation section only shows a GitHub dependency and never gives the public npm command. State the npm install command directly so public consumers can follow these instructions.
Every release published by `.github/workflows/publish.yml` is mirrored — the
exact same version — to the internal Azure Artifacts `copilot-canary` feed in
addition to public npm. Public consumers should just use npm (above); internal
consumers that need to resolve `@github/copilot-engine-sdk` from the feed can
point the `@github` scope at it and authenticate. Add an `.npmrc` next to your
`package.json`:
- Files reviewed: 4/4 changed files
- Comments generated: 0 new
- Review effort level: Medium
Addresses review: computing the version inside the publish job broke retry-safety and the same-version-everywhere invariant (a re-run would read the now-higher public version and publish a different one). Restructure into version -> publish -> {publish-internal, release}, mirroring copilot-agent-runtime's dedicated version job. Add overwrite: true to the tarball upload so the publish job is re-runnable in the same run, and make the GitHub release idempotent (skip if the tag exists). publish-internal and release are now independent so neither blocks the other.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b1cc5615-75ab-477b-9dbd-feff9d63a426
There was a problem hiding this comment.
Review details
Comments suppressed due to low confidence (2)
.github/workflows/publish.yml:280
- If
gh release createcreates the release but fails while uploading the tarball, a retry enters this branch and reports success while leaving the release incomplete. Upload the artifact with--clobberwhen the release already exists so partial failures are repaired.
if gh release view "$TAG" >/dev/null 2>&1; then
echo "Release $TAG already exists; skipping creation." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
README.md:44
- A scoped registry applies to every
@github/*dependency, not just this SDK, so this comment incorrectly says all other packages keep using the default registry. Clarify the boundary to prevent consumers from overlooking that other@githubpackages will also be routed to this feed.
# All other packages continue to resolve from your default registry.
- Files reviewed: 4/4 changed files
- Comments generated: 0 new
- Review effort level: Medium
The feed section was carried over from the earlier canary-only design, where the feed was the sole way to obtain a build and documenting its auth flow was load-bearing. Under dual-publish the feed receives the exact same version as public npm, so npm install covers effectively every consumer and the section documented a generic feed auth flow this package doesn't need. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b1cc5615-75ab-477b-9dbd-feff9d63a426
There was a problem hiding this comment.
Review details
Comments suppressed due to low confidence (1)
.github/workflows/publish.yml:280
- This skip makes release retries incomplete.
gh release createcreates the release before uploading its asset, so an upload failure can leave an existing release with no tarball; the retry then exits here permanently. Check the existing release's assets and upload the downloaded tarball when it is missing.
if gh release view "$TAG" >/dev/null 2>&1; then
echo "Release $TAG already exists; skipping creation." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
- Files reviewed: 3/3 changed files
- Comments generated: 1
- Review effort level: Medium
This comment was marked as low quality.
This comment was marked as low quality.
The idempotent publish wrapper treats an already-published version as success so retries are safe, but a conflict alone does not prove the registry holds this run's tarball. Assert the published dist.integrity matches the local tarball so publish-internal and release cannot propagate a different artifact under the same version. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b1cc5615-75ab-477b-9dbd-feff9d63a426
There was a problem hiding this comment.
Review details
Comments suppressed due to low confidence (2)
.github/workflows/publish.yml:250
- The idempotent wrapper can report success when this version already exists, but this internal path never confirms that the existing feed entry contains this tarball. A stale or out-of-band package can therefore make the workflow claim the mirror succeeded while the feed serves different bytes. As in the public publish path, compare the feed's
dist.integritywith the downloaded tarball before reporting success.
if bash "$GITHUB_WORKSPACE/script/npm-publish-idempotent.sh" --run "${tarballs[0]}" \
--registry "$FEED_URL" --tag latest; then
.github/workflows/publish.yml:301
- Skipping solely because the release exists is not retry-safe.
gh release createcreates the release before uploading its assets (using a draft during upload), so an upload failure can leave an existing draft or published release without this tarball; the retry then exits successfully without repairing it. Re-upload the asset with--clobberand publish any leftover draft before skipping creation.
if gh release view "$TAG" >/dev/null 2>&1; then
echo "Release $TAG already exists; skipping creation." >> "$GITHUB_STEP_SUMMARY"
exit 0
- Files reviewed: 3/3 changed files
- Comments generated: 0 new
- Review effort level: Medium
Creating a dedicated environment requires repo admin access. Two jobs can reference the same environment, so scoping the id-cpd-ci federated credential to repo:github/copilot-engine-sdk:environment:npm-publish removes that dependency and leaves only the identity admin's claim. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b1cc5615-75ab-477b-9dbd-feff9d63a426
There was a problem hiding this comment.
Review details
Comments suppressed due to low confidence (2)
.github/workflows/publish.yml:253
- The conflict wrapper can return success when this version already exists internally, but this job never verifies that the feed's existing
dist.integritymatches the downloaded tarball. A pre-existing version with different bytes would therefore be reported as successfully mirrored, violating the workflow's exact-tarball invariant. Please perform the same authenticated integrity check used for public npm before marking this job successful.
if bash "$GITHUB_WORKSPACE/script/npm-publish-idempotent.sh" --run "${tarballs[0]}" \
--registry "$FEED_URL" --tag latest; then
echo "Published internally (or already existed): @github/copilot-engine-sdk@${VERSION}" >> "$GITHUB_STEP_SUMMARY"
.github/workflows/publish.yml:304
gh release createcreates the release before uploading assets, so an asset-upload failure can leave the release present while the job fails. On a retry this branch exits successfully without restoring the missing tarball, making the new retry path incomplete. Upload (or verify) the tarball on the existing-release path before returning success.
if gh release view "$TAG" >/dev/null 2>&1; then
echo "Release $TAG already exists; skipping creation." >> "$GITHUB_STEP_SUMMARY"
exit 0
- Files reviewed: 3/3 changed files
- Comments generated: 0 new
- Review effort level: Medium
Mirrors every release to the internal Azure Artifacts
copilot-canaryfeed in addition to public npm — the same version and the same tarball, not a separate canary build — so internal consumers that can't pull from public npm can install the identical build. Modeled oncopilot-agent-runtime'spublish-cli.yml.Verified end-to-end: run 30403775246 published to the feed through this PR's actual
publish-internaljob (byte-identical, on a throwaway branch using a disposable package name). Public npm was untouched. The npm publish path itself is unchanged in substance and can't be dry-run.Changes
version→publish→ {publish-internal,release}.versionis isolated so a retry reuses the resolved version instead of reading the now-higher published one and cutting a different release.publishuploads the.tgzas an artifact;publish-internalpublishes that byte-identical tarball to the feed. Publishing a tarball also skipsprepare/prepublishOnly, so nothing rebuilds with feed credentials in scope.azure/login,az account get-access-token, masked, written to an auth-only.npmrc. It deliberately omits a scoped@github:registry=line, which is what keeps the explicit--registryauthoritative in both directions.script/npm-publish-idempotent.sh(new) — ported byte-for-byte from the runtime; treats an "already published" conflict as success so re-runs are safe. Used for both publishes..gitattributes(new) — keeps the shell script LF on Windows checkouts.Provisioning
Nothing new to create — both publish jobs reuse the existing
npm-publishenvironment. Already in place and confirmed by the run above: repo variablesCPD_ID_CLIENT_ID/CPD_ID_TENANT_ID, a federated credential onid-cpd-ciwith subjectrepo:github/copilot-engine-sdk:environment:npm-publish, and Feed Publisher oncopilot-canary.