fix(claude-md): remove over-verification guidance for Claude 5 models #1585
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Upgrade Test | |
| on: | |
| push: | |
| branches: [main, dev] | |
| pull_request: | |
| branches: [main, dev] | |
| workflow_dispatch: | |
| concurrency: | |
| group: upgrade-test-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| upgrade-test: | |
| name: omc update + session-start hook | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| # ── 1. Environment ────────────────────────────────────────────────────── | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: false | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| - name: Install npm dependencies | |
| run: npm ci | |
| # ── 2. Install Claude Code ──────────────────────────────────────────────── | |
| # Keep Claude Code installed in CI so the upgrade path still runs in a | |
| # realistic environment, but avoid relying on Claude runtime behavior for | |
| # hook verification. Step 6 validates the installed SessionStart hook | |
| # directly from repo-owned artifacts and therefore runs on push + PR. | |
| # ENOTEMPTY hardening: the toolcache global lib/node_modules dir is reused | |
| # across runs on GitHub-hosted runners (and inside the GitHub-hosted tool | |
| # cache). A previously interrupted `npm install -g` can leave the resolved | |
| # scoped package dir and/or npm's staging temp dirs behind, so the next | |
| # install fails with | |
| # npm error ENOTEMPTY: directory not empty, rename | |
| # '.../@anthropic-ai/claude-code' -> '.../@anthropic-ai/.claude-code-XXXX' | |
| # (observed in run 27492794574). Install Claude Code into an isolated npm | |
| # global prefix under RUNNER_TEMP so the install never depends on or | |
| # mutates the shared toolcache global dir, and retry with a fresh prefix to | |
| # absorb any residual rename race. A persistent failure still exits | |
| # non-zero (we never swallow the final failure). | |
| - name: Install Claude Code | |
| run: | | |
| set -euo pipefail | |
| CLAUDE_CODE_PREFIX="$RUNNER_TEMP/claude-code-global" | |
| reset_prefix() { | |
| rm -rf "${CLAUDE_CODE_PREFIX:?}" 2>/dev/null || true | |
| mkdir -p "$CLAUDE_CODE_PREFIX" | |
| } | |
| reset_prefix | |
| ATTEMPTS=3 | |
| INSTALLED=false | |
| for attempt in $(seq 1 "$ATTEMPTS"); do | |
| echo "Installing @anthropic-ai/claude-code (attempt $attempt/$ATTEMPTS)" | |
| if npm install -g --prefix "$CLAUDE_CODE_PREFIX" \ | |
| @anthropic-ai/claude-code --no-audit --no-fund; then | |
| INSTALLED=true | |
| break | |
| fi | |
| echo "Install attempt $attempt failed; recreating isolated prefix and retrying." | |
| reset_prefix | |
| sleep 2 | |
| done | |
| if [ "$INSTALLED" != "true" ]; then | |
| echo "FAIL: npm install -g @anthropic-ai/claude-code failed after $ATTEMPTS attempts" | |
| exit 1 | |
| fi | |
| # Expose the isolated prefix bin dir to this and subsequent steps so | |
| # `claude` resolves on PATH (e.g. the "Verify Claude Code is on PATH" | |
| # step's `which claude`). | |
| echo "$CLAUDE_CODE_PREFIX/bin" >> "$GITHUB_PATH" | |
| export PATH="$CLAUDE_CODE_PREFIX/bin:$PATH" | |
| claude --version | |
| - name: Verify Claude Code is on PATH | |
| run: which claude | |
| # ── 3. Install omc@4.9.3 via npm ──────────────────────────────────────── | |
| # Establishes the "old version" baseline before `omc update` is run. | |
| # | |
| # ENOTEMPTY hardening: the global node_modules dir is reused across runs on | |
| # GitHub-hosted runners (and within the GitHub-hosted tool cache). A | |
| # previously interrupted `npm install -g` can leave behind the resolved | |
| # package dir and/or npm's staging temp dirs (`.oh-my-claude-sisyphus-*`), | |
| # which makes the next install fail with | |
| # npm ERR! ENOTEMPTY: directory not empty, rename '.../oh-my-claude-sisyphus' | |
| # -> '.../.oh-my-claude-sisyphus-XXXXXXXX' | |
| # and, on rerun, appear to hang. Remove any stale global package + npm | |
| # staging dirs before installing, then retry the install a few times so a | |
| # transient rename race does not fail the run. Persistent install failures | |
| # still surface as a non-zero exit (we never swallow the final failure). | |
| - name: Install omc v4.9.3 via npm | |
| run: | | |
| set -euo pipefail | |
| PKG=oh-my-claude-sisyphus | |
| GLOBAL_ROOT="$(npm root -g)" | |
| clean_stale_global_pkg() { | |
| # Remove the resolved global package dir plus any npm staging temp | |
| # dirs left over from an interrupted global install. `:?` guards | |
| # against an empty GLOBAL_ROOT so we never rm -rf "/". | |
| rm -rf "${GLOBAL_ROOT:?}/$PKG" 2>/dev/null || true | |
| find "$GLOBAL_ROOT" -maxdepth 1 -name ".$PKG-*" -exec rm -rf {} + 2>/dev/null || true | |
| } | |
| clean_stale_global_pkg | |
| ATTEMPTS=3 | |
| for attempt in $(seq 1 "$ATTEMPTS"); do | |
| echo "Installing $PKG@4.9.3 (attempt $attempt/$ATTEMPTS)" | |
| if npm install -g "$PKG@4.9.3" --no-audit --no-fund; then | |
| echo "Installed $PKG@4.9.3" | |
| exit 0 | |
| fi | |
| echo "Install attempt $attempt failed; cleaning stale global state and retrying." | |
| clean_stale_global_pkg | |
| sleep 2 | |
| done | |
| echo "FAIL: npm install -g $PKG@4.9.3 failed after $ATTEMPTS attempts" | |
| exit 1 | |
| - name: Verify omc version is 4.9.3 | |
| run: | | |
| omc --version > "$RUNNER_TEMP/omc-pre-update.version" 2>&1 | |
| VERSION_EXIT=$? | |
| INSTALLED_VERSION=$(cat "$RUNNER_TEMP/omc-pre-update.version") | |
| if [ "$VERSION_EXIT" -ne 0 ]; then | |
| echo "FAIL: omc --version exited with code $VERSION_EXIT" | |
| cat "$RUNNER_TEMP/omc-pre-update.version" | |
| exit 1 | |
| fi | |
| echo "Installed omc version: $INSTALLED_VERSION" | |
| if [ "$INSTALLED_VERSION" != "4.9.3" ]; then | |
| echo "Expected version 4.9.3, got: $INSTALLED_VERSION" | |
| exit 1 | |
| fi | |
| # ── 4. Run omc update and verify clean stdout only ─────────────────────── | |
| # omc update must: | |
| # - Exit 0 | |
| # - Produce zero stderr | |
| # Any npm warnings, git warnings, or sync messages to stderr are failures. | |
| # Note: syncMarketplaceClone() is best-effort and skips gracefully when | |
| # the marketplace clone (~/.claude/plugins/marketplaces/omc/) doesn't exist, | |
| # so CI runners with a fresh home directory are handled correctly. | |
| # | |
| # dev can legitimately carry a package/release-notes version bump before | |
| # that version is published to npm. In that window the old 4.9.3 updater | |
| # still tries `npm install -g ...@latest`, which fails with ETARGET before | |
| # any current update/reconcile code can run. Keep the real `omc update` | |
| # path when the current package version exists on npm; otherwise install | |
| # the just-built local package tarball and run the same post-update | |
| # reconciliation command directly. This preserves deterministic coverage | |
| # for OMC-owned install, reconcile, and SessionStart artifacts without | |
| # requiring release publishing from CI. | |
| - name: Build current package for deterministic fallback | |
| run: npm run build | |
| - name: Prepare local package tarball | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| CURRENT_PACKAGE_VERSION="$(node -p "require('./package.json').version")" | |
| TARBALL_NAME="$(npm pack --pack-destination "$RUNNER_TEMP" --silent)" | |
| { | |
| echo "OMC_UPGRADE_TEST_EXPECTED_VERSION=$CURRENT_PACKAGE_VERSION" | |
| echo "OMC_UPGRADE_TEST_TARBALL=$RUNNER_TEMP/$TARBALL_NAME" | |
| } >> "$GITHUB_ENV" | |
| if npm view "oh-my-claude-sisyphus@$CURRENT_PACKAGE_VERSION" version >/dev/null 2>&1; then | |
| set +e | |
| GITHUB_LATEST_VERSION="$(node -e ' | |
| const headers = { "User-Agent": "omc-upgrade-test" }; | |
| if (process.env.GITHUB_TOKEN) headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`; | |
| fetch("https://api.github.com/repos/Yeachan-Heo/oh-my-claudecode/releases/latest", { headers }) | |
| .then(async response => { | |
| if (!response.ok) throw new Error(`${response.status} ${response.statusText}`); | |
| const release = await response.json(); | |
| process.stdout.write(String(release.tag_name || "").replace(/^v/, "")); | |
| }) | |
| .catch(error => { | |
| console.error(error instanceof Error ? error.message : String(error)); | |
| process.exit(1); | |
| }); | |
| ' 2>"$RUNNER_TEMP/github-latest-release.stderr")" | |
| GITHUB_LATEST_EXIT=$? | |
| set -e | |
| if [ "$GITHUB_LATEST_EXIT" -ne 0 ] || [ -z "$GITHUB_LATEST_VERSION" ]; then | |
| echo "OMC_UPGRADE_TEST_USE_LOCAL_TARBALL=true" >> "$GITHUB_ENV" | |
| echo "GitHub latest release lookup failed or returned empty; testing local tarball + update-reconcile." | |
| if [ -s "$RUNNER_TEMP/github-latest-release.stderr" ]; then | |
| echo "GitHub latest release lookup stderr:" | |
| cat "$RUNNER_TEMP/github-latest-release.stderr" | |
| fi | |
| elif [ "$GITHUB_LATEST_VERSION" = "$CURRENT_PACKAGE_VERSION" ]; then | |
| echo "OMC_UPGRADE_TEST_USE_LOCAL_TARBALL=false" >> "$GITHUB_ENV" | |
| echo "Package $CURRENT_PACKAGE_VERSION is published and GitHub latest matches; testing the live omc update path." | |
| else | |
| echo "OMC_UPGRADE_TEST_USE_LOCAL_TARBALL=true" >> "$GITHUB_ENV" | |
| echo "Package $CURRENT_PACKAGE_VERSION is published, but GitHub latest is $GITHUB_LATEST_VERSION; testing local tarball + update-reconcile." | |
| fi | |
| else | |
| echo "OMC_UPGRADE_TEST_USE_LOCAL_TARBALL=true" >> "$GITHUB_ENV" | |
| echo "Package $CURRENT_PACKAGE_VERSION is not published; testing local tarball + update-reconcile." | |
| fi | |
| - name: Run omc update | |
| id: omc-update | |
| env: | |
| # Suppress npm update progress bar to keep stdout clean | |
| npm_config_progress: 'false' | |
| run: | | |
| set -o pipefail | |
| # Capture exit code of omc update itself (or the deterministic | |
| # unpublished-version fallback). npm install may emit deprecation | |
| # warnings to stderr — those are not omc errors. Filter those out and | |
| # fail on everything else. | |
| set +e | |
| if [ "$OMC_UPGRADE_TEST_USE_LOCAL_TARBALL" = "true" ]; then | |
| { | |
| echo "Using local package tarball because deterministic fallback is required for $OMC_UPGRADE_TEST_EXPECTED_VERSION." | |
| npm install -g "$OMC_UPGRADE_TEST_TARBALL" | |
| omc update-reconcile | |
| } 1>"$RUNNER_TEMP/omc-update.stdout.log" 2>"$RUNNER_TEMP/omc-update.stderr.log" | |
| UPDATE_EXIT=$? | |
| else | |
| omc update 1>"$RUNNER_TEMP/omc-update.stdout.log" 2>"$RUNNER_TEMP/omc-update.stderr.log" | |
| UPDATE_EXIT=$? | |
| LIVE_UPDATED_VERSION="$(omc --version 2>/dev/null || true)" | |
| if [ "$LIVE_UPDATED_VERSION" = "4.9.3" ]; then | |
| { | |
| echo "Live omc update left version at 4.9.3; falling back to local tarball + update-reconcile." | |
| npm install -g "$OMC_UPGRADE_TEST_TARBALL" | |
| omc update-reconcile | |
| } >>"$RUNNER_TEMP/omc-update.stdout.log" 2>>"$RUNNER_TEMP/omc-update.stderr.log" | |
| UPDATE_EXIT=$? | |
| fi | |
| fi | |
| set -e | |
| # Check for non-npm stderr (omc warnings/errors/exceptions) | |
| # Exclude npm deprecation warnings: "npm deprecated ...". | |
| # All other stderr lines (omc console.warn, error, exception, fail patterns) are failures. | |
| if grep -vE '^npm deprecated ' "$RUNNER_TEMP/omc-update.stderr.log" 2>/dev/null | grep -qiE '^(error|exception|fail|critical|warn|omc)'; then | |
| echo "FAIL: omc update produced stderr:" | |
| cat "$RUNNER_TEMP/omc-update.stderr.log" | |
| echo "Full stdout:" | |
| cat "$RUNNER_TEMP/omc-update.stdout.log" | |
| exit 1 | |
| fi | |
| if [ "$UPDATE_EXIT" -ne 0 ]; then | |
| echo "FAIL: omc update exited with code $UPDATE_EXIT" | |
| echo "Full stdout:" | |
| cat "$RUNNER_TEMP/omc-update.stdout.log" | |
| echo "Full stderr:" | |
| cat "$RUNNER_TEMP/omc-update.stderr.log" | |
| exit 1 | |
| fi | |
| if [ "$OMC_UPGRADE_TEST_USE_LOCAL_TARBALL" = "true" ]; then | |
| echo "PASS: local tarball install + update-reconcile succeeded" | |
| else | |
| echo "PASS: omc update succeeded" | |
| fi | |
| # ── 5. Verify new omc version installed ────────────────────────────────── | |
| - name: Verify omc version after update | |
| run: | | |
| omc --version > "$RUNNER_TEMP/omc-post-update.version" 2>&1 | |
| VERSION_EXIT=$? | |
| UPDATED_VERSION=$(cat "$RUNNER_TEMP/omc-post-update.version") | |
| if [ "$VERSION_EXIT" -ne 0 ]; then | |
| echo "FAIL: omc --version exited with code $VERSION_EXIT" | |
| cat "$RUNNER_TEMP/omc-post-update.version" | |
| exit 1 | |
| fi | |
| echo "Updated omc version: $UPDATED_VERSION" | |
| if [ -z "$UPDATED_VERSION" ]; then | |
| echo "FAIL: omc --version returned empty" | |
| exit 1 | |
| fi | |
| if [ "$UPDATED_VERSION" = "4.9.3" ]; then | |
| echo "FAIL: omc version still 4.9.3 after update" | |
| exit 1 | |
| fi | |
| if [ "$OMC_UPGRADE_TEST_USE_LOCAL_TARBALL" = "true" ] && [ "$UPDATED_VERSION" != "$OMC_UPGRADE_TEST_EXPECTED_VERSION" ]; then | |
| echo "FAIL: local tarball fallback installed $UPDATED_VERSION, expected $OMC_UPGRADE_TEST_EXPECTED_VERSION" | |
| exit 1 | |
| fi | |
| echo "PASS: omc updated from 4.9.3 to $UPDATED_VERSION" | |
| # ── 6. Verify SessionStart hook wiring + execution ─────────────────────── | |
| # Validate the update-owned coverage directly: | |
| # - settings.json contains a SessionStart command for session-start.mjs | |
| # - the installed standalone hook script exists | |
| # - the hook script runs successfully on representative SessionStart JSON | |
| # | |
| # This keeps upgrade coverage inside OMC-owned artifacts and avoids relying | |
| # on external Claude CLI print-mode behavior in headless CI. | |
| - name: Verify SessionStart hook wiring and script | |
| run: | | |
| set -o pipefail | |
| CLAUDE_CONFIG_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}" | |
| SESSION_HOOK="$CLAUDE_CONFIG_DIR/hooks/session-start.mjs" | |
| SETTINGS_PATH="$CLAUDE_CONFIG_DIR/settings.json" | |
| if [ ! -f "$SESSION_HOOK" ]; then | |
| echo "FAIL: missing SessionStart hook script at $SESSION_HOOK" | |
| exit 1 | |
| fi | |
| if [ ! -f "$SETTINGS_PATH" ]; then | |
| echo "FAIL: missing Claude settings at $SETTINGS_PATH" | |
| exit 1 | |
| fi | |
| SESSION_COMMAND="$(node -e ' | |
| const fs = require("fs"); | |
| const settings = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); | |
| const groups = settings.hooks?.SessionStart ?? []; | |
| const command = groups | |
| .flatMap(group => group?.hooks ?? []) | |
| .find(hook => hook?.type === "command" && typeof hook.command === "string" && hook.command.includes("session-start.mjs")) | |
| ?.command ?? ""; | |
| process.stdout.write(command); | |
| ' "$SETTINGS_PATH")" | |
| if [ -z "$SESSION_COMMAND" ]; then | |
| echo "FAIL: settings.json is missing a SessionStart command for session-start.mjs" | |
| cat "$SETTINGS_PATH" | |
| exit 1 | |
| fi | |
| set +e | |
| printf '%s' \ | |
| "{\"hook_event_name\":\"SessionStart\",\"session_id\":\"ci-upgrade-test\",\"cwd\":\"$GITHUB_WORKSPACE\"}" \ | |
| | node "$SESSION_HOOK" 1>"$RUNNER_TEMP/session.stdout.log" 2>"$RUNNER_TEMP/session.stderr.log" | |
| SESSION_EXIT=$? | |
| set -e | |
| if [ "$SESSION_EXIT" -ne 0 ]; then | |
| echo "FAIL: SessionStart hook exited with code $SESSION_EXIT" | |
| echo "stderr:" | |
| cat "$RUNNER_TEMP/session.stderr.log" | |
| echo "stdout:" | |
| cat "$RUNNER_TEMP/session.stdout.log" | |
| exit 1 | |
| fi | |
| if [ -s "$RUNNER_TEMP/session.stderr.log" ]; then | |
| echo "FAIL: SessionStart hook produced stderr:" | |
| cat "$RUNNER_TEMP/session.stderr.log" | |
| echo "stdout:" | |
| cat "$RUNNER_TEMP/session.stdout.log" | |
| exit 1 | |
| fi | |
| node -e ' | |
| const fs = require("fs"); | |
| const output = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); | |
| if (output.continue !== true) { | |
| throw new Error("SessionStart hook did not return continue=true"); | |
| } | |
| const hookName = output.hookSpecificOutput?.hookEventName; | |
| if (hookName !== undefined && hookName !== "SessionStart") { | |
| throw new Error(`Unexpected hookSpecificOutput.hookEventName: ${hookName}`); | |
| } | |
| ' "$RUNNER_TEMP/session.stdout.log" | |
| echo "PASS: SessionStart hook is configured and executes cleanly" |