feat: add daily CLI smoke test pipeline#551
Conversation
Scheduled workflow that runs pack → publish → deploy → health check daily at 9 AM IST using both stable (npm) and alpha (GH Packages) CLI versions against alpha env. Posts to #coded-apps-alerts on failure. Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
| ORG: popoc | ||
| TENANT: adetenant | ||
| FOLDER_KEY: 8645d674-92d8-4281-9aef-43f3e3608ded |
There was a problem hiding this comment.
These three values are hardcoded in the workflow file and will be committed to git history. ORG and TENANT expose internal alpha-environment infrastructure details; FOLDER_KEY is a GUID that could be used to make API calls in the wrong environment if reused elsewhere.
Use repository variables instead so they can be rotated/changed without a code commit:
| ORG: popoc | |
| TENANT: adetenant | |
| FOLDER_KEY: 8645d674-92d8-4281-9aef-43f3e3608ded | |
| ORG: ${{ vars.SMOKE_TEST_ORG }} | |
| TENANT: ${{ vars.SMOKE_TEST_TENANT }} | |
| FOLDER_KEY: ${{ vars.SMOKE_TEST_FOLDER_KEY }} |
| --name "$APP_NAME" \ | ||
| --version "$VERSION" | ||
|
|
||
| - name: Deploy |
There was a problem hiding this comment.
The workflow deploys an app to the alpha environment on every run but never cleans it up. Daily runs will accumulate deployed versions indefinitely. Add an explicit cleanup step that runs even on failure — either a dedicated uip codedapp undeploy step after health-check, or an if: always() cleanup job that runs after the test jobs.
Example (add as the last step in each job):
- name: Cleanup
if: always()
run: |
uip codedapp undeploy \
--name "$APP_NAME" \
--folder-key "${{ env.FOLDER_KEY }}" || true| alpha-cli: | ||
| name: Alpha CLI (GH Packages) | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| packages: read | ||
| env: | ||
| APP_NAME: cli-smoke-alpha | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '20' | ||
|
|
||
| - name: Install CLI (GH Packages) | ||
| run: | | ||
| echo "//npm.pkg.github.com/:_authToken=${{ secrets.GH_PACKAGES_TOKEN }}" >> ~/.npmrc | ||
| echo "@uipath:registry=https://npm.pkg.github.com" >> ~/.npmrc | ||
| npm i -g @uipath/cli | ||
| uip tools update | ||
| echo "CLI version: $(uip --version)" | ||
| uip tools list | ||
|
|
||
| - name: Login | ||
| run: | | ||
| uip login \ | ||
| --client-id "${{ secrets.UIPATH_CLIENT_ID }}" \ | ||
| --client-secret "${{ secrets.UIPATH_CLIENT_SECRET }}" \ | ||
| --authority "${{ env.ALPHA_AUTHORITY }}" \ | ||
| --tenant "${{ env.TENANT }}" | ||
|
|
||
| - name: Pack | ||
| run: | | ||
| VERSION="0.0.$(date +%Y%m%d)" | ||
| echo "VERSION=$VERSION" >> "$GITHUB_ENV" | ||
| uip codedapp pack \ | ||
| --name "$APP_NAME" \ | ||
| --version "$VERSION" \ | ||
| fixtures/smoke-test-app | ||
|
|
||
| - name: Publish | ||
| run: | | ||
| uip codedapp publish \ | ||
| --name "$APP_NAME" \ | ||
| --version "$VERSION" | ||
|
|
||
| - name: Deploy | ||
| run: | | ||
| uip codedapp deploy \ | ||
| --name "$APP_NAME" \ | ||
| --folder-key "${{ env.FOLDER_KEY }}" | ||
|
|
||
| - name: Health check | ||
| run: | | ||
| APP_URL="https://${{ env.ORG }}.uipath.host/${APP_NAME}" | ||
| echo "Checking $APP_URL" | ||
| for i in 1 2 3; do | ||
| if curl -sf --max-time 10 "$APP_URL" > /dev/null; then | ||
| echo "Health check passed on attempt $i" | ||
| exit 0 | ||
| fi | ||
| echo "Attempt $i failed, retrying in 10s..." | ||
| sleep 10 | ||
| done | ||
| echo "Health check failed after 3 attempts" | ||
| exit 1 |
There was a problem hiding this comment.
The alpha-cli job is a verbatim copy of stable-cli — 5 of 6 steps are identical. Only the "Install CLI" step differs. Any future change to Pack, Publish, Deploy, or Health-check steps must be made in two places, which is an ongoing maintenance hazard.
Refactor using a matrix strategy:
jobs:
smoke:
name: ${{ matrix.label }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- label: Stable CLI (npm @latest)
app_name: cli-smoke-stable
install_from_gh_packages: false
- label: Alpha CLI (GH Packages)
app_name: cli-smoke-alpha
install_from_gh_packages: true
env:
APP_NAME: ${{ matrix.app_name }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install CLI
run: |
if [ "${{ matrix.install_from_gh_packages }}" = "true" ]; then
echo "//npm.pkg.github.com/:_authToken=${{ secrets.GH_PACKAGES_TOKEN }}" >> ~/.npmrc
echo "@uipath:registry=https://npm.pkg.github.com" >> ~/.npmrc
npm i -g @uipath/cli
else
npm i -g @uipath/cli@latest
fi
uip tools update
# ... shared Pack / Publish / Deploy / Health-check / Cleanup stepsThis also naturally handles the packages: read permission — it can be set at the job level with if: matrix.install_from_gh_packages or simply granted for all matrix legs since the stable leg never hits npm.pkg.github.com.
Review summaryFour issues found this run:
|
Temporary — remove before merging to main. Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
tools update fails when no tools are installed. Use tools install codedapp for the initial setup on a fresh runner. Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
| - cron: '30 3 * * *' | ||
| workflow_dispatch: {} # manual trigger for testing | ||
| push: | ||
| branches: [feat/cli-smoke-test] # temporary: remove before merging to main |
There was a problem hiding this comment.
This trigger is correctly flagged as "temporary" in the inline comment, but it lives in the file that's being merged. If this PR is approved and merged without removing these lines first, the feat/cli-smoke-test branch will no longer exist — so there's no immediate harm — but if anyone creates a branch with that same name in the future they'll trigger full alpha deploys on every push.
More importantly, the # temporary: remove before merging to main comment is easy to miss during a busy merge. Consider removing it now (in this PR) rather than relying on remembering to do it later:
| branches: [feat/cli-smoke-test] # temporary: remove before merging to main |
(Remove the push: trigger block entirely — schedule: + workflow_dispatch: are the two triggers this workflow needs long-term.)
| run: | | ||
| uip codedapp deploy \ | ||
| --name "$APP_NAME" \ | ||
| --folder-key "${{ env.FOLDER_KEY }}" |
There was a problem hiding this comment.
The deploy step doesn't pin --version "$VERSION", so the CLI will deploy whatever it considers the "latest" published version. Today this is harmless because both jobs publish the same date-based version string. But once the version-collision fix (seconds-precision, separate open thread) lands, each job will publish a unique version. If a second run's Publish step completes between this run's Publish and Deploy steps, this job silently deploys the wrong version — and the health-check passes, masking the regression the pipeline is supposed to catch.
Pin the version to match what was just published:
| --folder-key "${{ env.FOLDER_KEY }}" | |
| --folder-key "${{ env.FOLDER_KEY }}" \ | |
| --version "$VERSION" |
Same fix is needed in the alpha-cli job's Deploy step.
Review summaryTwo new findings this run (four prior threads remain open):
|
- Add --organization, --scope to login (Apps OR.Folders.Read OR.Execution) - Pass credentials via env vars instead of inline secrets - Add --path-name to deploy - Fix pack command argument order Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
The built-in GITHUB_TOKEN has read:packages for same-org packages, matching how publish.yml authenticates. Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
| jobs: | ||
| stable-cli: | ||
| name: Stable CLI (npm @latest) | ||
| runs-on: ubuntu-latest |
There was a problem hiding this comment.
Neither stable-cli nor alpha-cli sets timeout-minutes. GitHub's default is 6 hours, so a hung uip codedapp deploy, uip codedapp publish, or network stall will silently occupy a runner until the run is force-cancelled — which could pile up for a daily workflow. Add a reasonable upper bound (30 minutes covers the full lifecycle with headroom):
| runs-on: ubuntu-latest | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 |
Same addition needed for the alpha-cli job at its runs-on line.
Review summaryTwo new findings this run (six prior threads remain open):
|
--path-name doesn't exist in the alpha CLI and is redundant when equal to --name. Also fixes "routing name must be unique" error on upgrade runs where getDeployedApp lookup missed the existing app. Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
App name includes github.run_number (smoke-s-42, smoke-a-42) so every run does a fresh publish + fresh deploy with no collisions. Version is always 1.0.0 since the name is unique. Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
Review summaryOne new finding this run (eight prior threads remain open):
|
Use bot token + chat.postMessage instead of incoming webhook. Temporarily set to always() to test Slack delivery. Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
| echo "text=:red_circle: *CLI Smoke Test Failed*\nJobs: ${FAILED_JOBS}\nRun: ${RUN_URL}" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Post to Slack | ||
| uses: slackapi/slack-github-action@v3 |
Slack delivery confirmed working. Notify job now only fires on failure. Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
| name: Slack Alert on Failure | ||
| runs-on: ubuntu-latest | ||
| needs: [stable-cli, alpha-cli] | ||
| if: failure() |
There was a problem hiding this comment.
Same concern as the push trigger on line 9 — this if: always() is marked "temporary" but will be merged as-is.
With if: always(), the notify job runs on every run, including successful ones. On a success run FAILED_JOBS will be empty, so Slack receives:
:red_circle: *CLI Smoke Test Failed*
Jobs:
Run: https://...
That's a misleading "CLI Smoke Test Failed" alert with no failed jobs listed, firing every day even when everything is green.
The PR description explicitly says "Posts to #coded-apps-alerts via Slack webhook on failure only". Apply the fix from the open cancelled-jobs thread and set the final condition now:
| if: failure() | |
| if: failure() || contains(needs.*.result, 'cancelled') |
This removes the "temporary" state before merge and also incorporates the cancelled-job fix the other open thread requests.
| uses: slackapi/slack-github-action@v3 | ||
| with: | ||
| method: chat.postMessage | ||
| token: ${{ secrets.SLACK_BOT_TOKEN }} |
There was a problem hiding this comment.
Two problems here:
1. Secret name mismatch with the PR description
The setup table in the PR description documents SLACK_WEBHOOK_URL (an incoming webhook), but the implementation uses SLACK_BOT_TOKEN (a bot token for the chat.postMessage API method). These are entirely different Slack authentication methods. If the repo only has SLACK_WEBHOOK_URL configured (as the docs say), SLACK_BOT_TOKEN will resolve to an empty string and the step will fail with an authentication error. The PR description's setup table needs to document SLACK_BOT_TOKEN (not SLACK_WEBHOOK_URL).
2. "Gracefully skipped if missing" is not implemented
The PR description says: "optional, gracefully skipped if missing". But there is no if: condition on this step — if SLACK_BOT_TOKEN is absent, the step runs and fails. Add a guard to match the documented behavior:
| token: ${{ secrets.SLACK_BOT_TOKEN }} | |
| - name: Post to Slack | |
| if: ${{ secrets.SLACK_BOT_TOKEN != '' }} | |
| uses: slackapi/slack-github-action@v3 | |
| with: | |
| method: chat.postMessage | |
| token: ${{ secrets.SLACK_BOT_TOKEN }} |
(And update the setup table to list SLACK_BOT_TOKEN, not SLACK_WEBHOOK_URL.)
Review summaryTwo new findings this run (nine prior threads remain open):
|
Review summaryOne new finding this run (ten prior threads remain open): Slack secret type mismatch (line 187) — The PR description's setup table lists |
…trigger - Remove push trigger on feature branch - Point Slack alerts to #coded-apps-alerts (C0AMP09RXH7) Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
| method: chat.postMessage | ||
| token: ${{ secrets.SLACK_BOT_TOKEN }} | ||
| payload: | | ||
| channel: "C0AMP09RXH7" |
There was a problem hiding this comment.
The Slack channel ID is hardcoded. Like ORG, TENANT, and FOLDER_KEY (see open thread on line 16), this is internal infrastructure detail committed to the repo. If the channel is archived or the workspace migrates, this silently breaks all failure alerts.
Move it to a repository variable:
| channel: "C0AMP09RXH7" | |
| channel: "${{ vars.SMOKE_TEST_SLACK_CHANNEL_ID }}" |
Then add SMOKE_TEST_SLACK_CHANNEL_ID to the setup table in the PR description alongside the other required variables.
failure() doesn't catch cancelled jobs. Added cancelled check to both the notify condition and the failed-jobs message builder. Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
Review summaryOne new finding this run (twelve prior threads remain open): Hardcoded Slack channel ID (line 187) — |
Review summaryFour previously-resolved threads were reopened because the requested changes were not applied in the current diff:
Items that were fixed and stay resolved: version collision (now uses hardcoded |
|



Summary
pack → publish → deploy → health check) daily at 9 AM IST@latest) and alpha CLI (GH Packages) — both deploy coded app against alpha env#coded-apps-alertsSlack channel on failure only