Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .claude/agent-memory/archgate-developer/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Skipping steps 2 or 3 is a workflow violation. The user should NEVER have to inv
- [Pick the right enforcement layer](feedback_prefer_tests_over_adr_rules.md) — static syntactic invariants → custom oxlint rule in `.archgate/lint/oxlint.ts`; tests are for executable behavior; ADR .rules.ts for cross-file/governance checks

- [This repo is PUBLIC — no private sibling-repo internals in memory/PRs](feedback_public_repo_privacy.md) — split captures: public-safe summary here, full detail in the private repo's own memory
- [Keep code comments and memory entries concise](feedback_concise_comments.md) — no multi-paragraph rationale blocks; one line + terse why, link out for detail

## Known Bugs

Expand Down Expand Up @@ -70,6 +71,9 @@ Non-enforceable lessons — environment/CI/platform quirks no static rule can re
- **`Bun.Glob.scan()` silently fails for brace patterns with path separators** — `new Bun.Glob("svc/{src/env.ts,env.ts}").scan(...)` returns zero results (no error), while `.match()` works correctly for the same pattern. The match engine was rewritten in Bun 1.2.3 (PR #16824) to expand braces recursively, but the scanner (`GlobWalker.zig`) was not updated. Filed upstream as [oven-sh/bun#32596](https://github.com/oven-sh/bun/issues/32596). Workaround: `expandBracePattern()` in `src/engine/runner.ts` pre-expands brace groups containing `/` before scanning. Applied in `ctx.glob()`, `ctx.grepFiles()`, and `resolveScopedFiles()`.
- **ARCH-020 `glob-scan-dot` rule triggers on `.scan()` in comments** — The rule uses regex `/\.scan\(([^)]*)\)/gu` which matches `.scan()` text in JSDoc/inline comments (e.g., `Bun.Glob.scan() silently...`). Workaround: rephrase comments to avoid the exact `.scan()` text — e.g., "Bun.Glob scanning silently..." instead of "Bun.Glob.scan() silently...".
- **GitHub Actions `secrets.*` and `vars.*` are separate namespaces — configuring a value as one does NOT make it readable via the other** — `release.yml`'s "Annotate release in PostHog" step read `POSTHOG_PROJECT_ID` via `${{ vars.POSTHOG_PROJECT_ID }}`, but the value was only ever configured as a repo **secret** (confirmed with `gh secret list` vs `gh variable list` — zero repo variables existed). `vars.POSTHOG_PROJECT_ID` therefore always resolved to an empty string. Combined with `continue-on-error: true`, an internal guard that does `exit 0` (not a failure) when required config is missing, and a low-visibility `::notice::` log line, the step silently no-opped on every release for ~3 weeks (9 releases, v0.45.0–v0.45.7) with zero CI failures to flag it — confirmed via `gh run view <id> --log` showing `POSTHOG_PROJECT_ID: ` blank in the step's masked env dump. Before wiring a new `secrets.X`/`vars.X` reference, verify with `gh secret list`/`gh variable list` which namespace actually holds the value. For any `continue-on-error` step with a skip-on-missing-config guard, use `::warning::` (not `::notice::`) so misconfiguration surfaces in the Actions UI instead of being invisible indefinitely. Also documented in the project's `CLAUDE.md`.
- **Release workflow chaining:** `release-binaries.yml` dispatches `publish-shims.yml` after binaries + provenance succeed, avoiding a `release: published` race. See [[project_release_pipeline_gotchas]].
- **`moonrepo/setup-toolchain` cache can break PATH** after a `.prototools` bump; self-heals on retry. See [[project_release_pipeline_gotchas]].
- **CLI update-check notice can pollute stdout** — gated via `shouldPerformUpdateCheck()`. See [[project_release_pipeline_gotchas]].

## Claude Code Harness Config

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
name: feedback-concise-comments
description: Keep code comments and memory entries concise — do not overgenerate explanatory prose
metadata:
type: feedback
---

Code comments and memory entries must be concise. Do not write multi-paragraph explanatory comments in source/workflow files, and do not write long-winded memory bullets.

**Why:** User feedback via `/feedback` (2026-07-03): "sonnet is overgenerating comments in code and memories. this is not good. those comments must be concise." Given after a session with long comment blocks in workflow files and `src/cli.ts`, and multi-sentence [[MEMORY.md]] bullets with full incident narratives.

**How to apply:**

- Code/workflow comments: one line stating _what_ and, if truly non-obvious, a terse _why_ — not a paragraph with timelines or backstory. Link to a PR/issue/commit for detail instead of inlining it.
- Memory entries (`MEMORY.md` bullets and topic files): lead with the rule in one line; keep **Why:**/**How to apply:** to single short sentences, not multi-clause narratives with timestamps and evidence trails.
- If tempted to write a long comment or memory entry to "preserve context," prefer a short pointer (file/PR reference) over inlining the full story.
- Applies to all future sessions in this repo — re-check comment/memory length before writing.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
name: project-release-pipeline-gotchas
description: Three causes behind archgate/cli release publishing failures found 2026-07-03
metadata:
type: project
---

Three distinct issues caused shim/binary publishing failures across v0.45.1–v0.46.1, fixed in `fix/publish-shims-race`:

1. **Workflow race**: `publish-shims.yml` and `release-binaries.yml` both triggered on `release: published`. If binaries needed a retry, `publish-shims.yml`'s fixed-budget `wait-for-binaries` poll timed out and went `cancelled` (terminal) before the retry finished. Fixed: `publish-shims.yml` is now `workflow_dispatch`-only; `release-binaries.yml`'s `trigger-shim-publish` job dispatches it via `gh workflow run` after binaries + provenance succeed.
2. **`moonrepo/setup-toolchain` cache bug**: right after a `.prototools` bump, the first macOS/Windows CI run can restore a stale `restore-key` cache fallback instead of an exact hit — the action reports success but `bun` isn't on PATH. Log signature: `Cache hit for restore-key:` (vs. exact `Cache hit for:`). Self-heals on retry (the failing job still saves a fresh exact-key cache in post-job cleanup). No code fix — just don't chase it as flakiness.
3. **Update-check stdout pollution**: `src/cli.ts` printed a background "update available" notice to stdout after every command, unconditionally. Broke `JSON.parse(stdout)` in CLI-subprocess tests (and would break real `| jq` usage) whenever a newer release existed. Fixed via `shouldPerformUpdateCheck()` in `src/helpers/update-check.ts` — only checks in a genuine TTY, non-CI session.
4. **`publish-go-tag` missing `workflows: write`**: its `git push origin shims/go/$TAG` was rejected — GitHub blocks any ref push reachable through commits touching `.github/workflows/*` without that permission, even for an unrelated tag. Fixed by adding `workflows: write` to the job's `permissions:`. Confirmed on v0.45.7's job log.

**Why:** Diagnosed while investigating why v0.46.0/v0.46.1 kept failing to publish despite repeated manual retries.
**How to apply:** If a release build fails and a retry "just works," check which of these three it was before assuming generic flakiness.
41 changes: 40 additions & 1 deletion .github/workflows/code-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,35 @@ jobs:
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
uses: ./.github/workflows/smoke-test-linux.yml

# Uploads findings to the Security tab (GitHub Advanced Security, free for
# public repos) rather than failing the build — there's an existing
# unreviewed findings backlog across several workflow files. Once that's
# triaged, this can be promoted to a hard gate via code-scanning merge
# protection (repo Settings, not this workflow).
zizmor:
name: Zizmor (Workflow Security)
runs-on: ubuntu-latest
timeout-minutes: 5
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
permissions:
contents: read # actions/checkout
security-events: write
Comment thread
cursor[bot] marked this conversation as resolved.
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
persist-credentials: false
- name: Run zizmor
# Fork PRs get a read-only GITHUB_TOKEN regardless of the
# `permissions:` above, so SARIF upload would fail — fall back to
# console-only output and don't let that (or the pre-existing
# findings backlog) fail the gate for external contributors.
uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7
continue-on-error: ${{ github.event.pull_request.head.repo.fork == true }}
with:
config: zizmor.yml
advanced-security: ${{ github.event.pull_request.head.repo.fork != true }}

coverage:
name: Coverage Report
runs-on: ubuntu-latest
Expand Down Expand Up @@ -236,7 +265,15 @@ jobs:
runs-on: ubuntu-latest
if: always()
needs:
[validate, shim-changes, shim-tests, smoke-windows, smoke-linux, coverage]
[
validate,
shim-changes,
shim-tests,
smoke-windows,
smoke-linux,
zizmor,
coverage,
]
steps:
- name: Check job results
run: |
Expand All @@ -248,12 +285,14 @@ jobs:
[[ "$SHIM_OK" != "success" ]] || \
[[ "${{ needs.smoke-windows.result }}" != "success" ]] || \
[[ "${{ needs.smoke-linux.result }}" != "success" ]] || \
[[ "${{ needs.zizmor.result }}" != "success" ]] || \
[[ "${{ needs.coverage.result }}" != "success" ]]; then
echo "::error::One or more jobs failed:"
echo " validate: ${{ needs.validate.result }}"
echo " shim-tests: ${{ needs.shim-tests.result }}"
echo " smoke-windows: ${{ needs.smoke-windows.result }}"
echo " smoke-linux: ${{ needs.smoke-linux.result }}"
echo " zizmor: ${{ needs.zizmor.result }}"
Comment thread
cursor[bot] marked this conversation as resolved.
echo " coverage: ${{ needs.coverage.result }}"
exit 1
fi
Expand Down
50 changes: 32 additions & 18 deletions .github/workflows/publish-shims.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
name: Publish Shims

# No `release: published` trigger — release-binaries.yml dispatches this via
# `gh workflow run` once binaries are actually uploaded. Avoids racing a
# fixed-budget wait against a build that may need a retry (see CLAUDE.md).
on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
Expand All @@ -15,21 +16,21 @@ env:
ARCHGATE_TELEMETRY: "0"

jobs:
# Wait for platform binaries to be uploaded by release-binaries.yml.
# Shim packages download binaries on first use, but smoke tests need them.
# Sanity-check assets are present (covers GitHub API read-after-write lag).
wait-for-binaries:
name: Wait for binaries
runs-on: ubuntu-latest
timeout-minutes: 30
timeout-minutes: 5
permissions:
contents: read
steps:
- name: Wait for release assets
- name: Verify release assets are present
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ inputs.tag }}
run: |
TAG="${{ github.event.release.tag_name || inputs.tag }}"
REQUIRED_ASSETS=("archgate-darwin-arm64.tar.gz" "archgate-linux-x64.tar.gz" "archgate-win32-x64.zip")
for i in $(seq 1 60); do
for i in $(seq 1 10); do
Comment thread
coderabbitai[bot] marked this conversation as resolved.
ASSETS=$(gh release view "$TAG" --repo archgate/cli --json assets --jq '.assets[].name')
ALL_FOUND=true
for asset in "${REQUIRED_ASSETS[@]}"; do
Expand All @@ -42,10 +43,12 @@ jobs:
echo "All required release assets found"
exit 0
fi
echo "Waiting for release assets... attempt $i/60"
sleep 30
echo "Waiting for release assets... attempt $i/10"
sleep 15
done
echo "::error::Timed out waiting for release assets"
echo "::error::Required binaries for $TAG are still missing after 10 checks (~2.5 min)."
echo "::error::This workflow expects release-binaries.yml to have already succeeded for this tag."
echo "::error::Check the Release Binaries run for $TAG, then re-run: gh workflow run publish-shims.yml -f tag=$TAG"
exit 1

publish-pypi:
Expand All @@ -57,7 +60,8 @@ jobs:
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ github.event.release.tag_name || inputs.tag }}
ref: ${{ inputs.tag }}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
persist-credentials: false
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
python-version: "3.12"
Expand All @@ -80,7 +84,8 @@ jobs:
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ github.event.release.tag_name || inputs.tag }}
ref: ${{ inputs.tag }}
persist-credentials: false
- uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5
with:
dotnet-version: "8.0.x"
Expand All @@ -91,25 +96,32 @@ jobs:
user: rhuan.barreto
- name: Pack and publish
working-directory: shims/nuget/Archgate.Tool
env:
NUGET_API_KEY: ${{ steps.nuget-login.outputs.NUGET_API_KEY }}
run: |
dotnet pack -c Release
dotnet nuget push bin/Release/*.nupkg \
--source https://api.nuget.org/v3/index.json \
--api-key ${{ steps.nuget-login.outputs.NUGET_API_KEY }}
--api-key "$NUGET_API_KEY"

publish-go-tag:
needs: wait-for-binaries
runs-on: ubuntu-latest
timeout-minutes: 5
# workflows: write is required to push a new ref — GitHub rejects any
# ref push reachable through commits that touch .github/workflows/*
# without it, even for an unrelated tag like this one.
permissions:
contents: write
workflows: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ github.event.release.tag_name || inputs.tag }}
ref: ${{ inputs.tag }}
- name: Create Go module tag
env:
TAG: ${{ inputs.tag }}
run: |
TAG="${{ github.event.release.tag_name || inputs.tag }}"
GO_TAG="shims/go/${TAG}"
git tag "$GO_TAG"
git push origin "$GO_TAG"
Expand All @@ -122,7 +134,8 @@ jobs:
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ github.event.release.tag_name || inputs.tag }}
ref: ${{ inputs.tag }}
persist-credentials: false
- uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5
with:
distribution: "temurin"
Expand Down Expand Up @@ -151,7 +164,8 @@ jobs:
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ github.event.release.tag_name || inputs.tag }}
ref: ${{ inputs.tag }}
persist-credentials: false
- uses: ruby/setup-ruby@9eb537ca036ebaed86729dcb9309076e4c5c3b74 # v1.314.0
with:
ruby-version: "4.0.5"
Expand Down
30 changes: 25 additions & 5 deletions .github/workflows/release-binaries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ jobs:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ github.event.release.tag_name || inputs.tag }}
persist-credentials: false

- uses: moonrepo/setup-toolchain@261c62cb5b0f580c7be7c8cd0f023a2e96756095 # v0
with:
Expand Down Expand Up @@ -98,9 +99,10 @@ jobs:
if: runner.os != 'Windows'
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.event.release.tag_name || inputs.tag }}
BUNDLE_PATH: ${{ steps.attest-unix.outputs.bundle-path }}
run: |
TAG="${{ github.event.release.tag_name || inputs.tag }}"
cp "${{ steps.attest-unix.outputs.bundle-path }}" "${{ matrix.artifact }}.tar.gz.sigstore.json"
cp "$BUNDLE_PATH" "${{ matrix.artifact }}.tar.gz.sigstore.json"
gh release upload "$TAG" \
"${{ matrix.artifact }}.tar.gz" \
"${{ matrix.artifact }}.tar.gz.sha256" \
Expand All @@ -112,10 +114,11 @@ jobs:
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.event.release.tag_name || inputs.tag }}
BUNDLE_PATH: ${{ steps.attest-win.outputs.bundle-path }}
run: |
$tag = "${{ github.event.release.tag_name || inputs.tag }}"
Copy-Item "${{ steps.attest-win.outputs.bundle-path }}" "${{ matrix.artifact }}.zip.sigstore.json"
gh release upload $tag "${{ matrix.artifact }}.zip" "${{ matrix.artifact }}.zip.sha256" "${{ matrix.artifact }}.zip.sigstore.json" --clobber
Copy-Item "$env:BUNDLE_PATH" "${{ matrix.artifact }}.zip.sigstore.json"
gh release upload "$env:TAG" "${{ matrix.artifact }}.zip" "${{ matrix.artifact }}.zip.sha256" "${{ matrix.artifact }}.zip.sigstore.json" --clobber

- name: Upload digest for SLSA provenance (Unix)
if: runner.os != 'Windows'
Expand Down Expand Up @@ -171,3 +174,20 @@ jobs:
base64-subjects: "${{ needs.combine-hashes.outputs.digests }}"
upload-assets: true
upload-tag-name: ${{ github.event.release.tag_name || inputs.tag }}

# Dispatch publish-shims.yml once binaries + provenance are confirmed
# uploaded, instead of racing it via a shared `release: published` trigger.
trigger-shim-publish:
name: Trigger shim publishing
needs: provenance
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
actions: write # required by `gh workflow run` to dispatch publish-shims.yml
steps:
- name: Dispatch publish-shims.yml
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.event.release.tag_name || inputs.tag }}
run: |
gh workflow run publish-shims.yml --repo archgate/cli -f tag="$TAG"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Loading
Loading