From 148d9443ca8c406256789a0b2f70467374a73b47 Mon Sep 17 00:00:00 2001 From: d-oit <6849456+d-oit@users.noreply.github.com> Date: Tue, 11 Aug 2026 21:32:47 +0200 Subject: [PATCH] docs: record CI/CD learnings in plans and lessons --- AGENTS.md | 18 +++++ agents-docs/LESSONS.md | 73 +++++++++++++++++ agents-docs/lessons.jsonl | 3 + ...itleaks-license-pre-existing-2026-08-11.md | 78 ++++++++++++++----- plans/116-ci-workflow-learnings-2026-08-11.md | 68 ++++++++++++++++ 5 files changed, 222 insertions(+), 18 deletions(-) create mode 100644 plans/116-ci-workflow-learnings-2026-08-11.md diff --git a/AGENTS.md b/AGENTS.md index ae5ce058..abd25270 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -276,6 +276,24 @@ When merging dependabot PRs or manually bumping dependencies: 4. **UI library major bumps** (shadcn primitives, react-resizable-panels, radix) may rename exports — check `pnpm run build` for type errors. 5. **After merging any dependabot PR**, immediately run the full quality workflow including `pnpm run build` and push a fix if needed. +## Learnings (session-distilled) + +- **gitleaks-action v3+ requires a paid `GITLEAKS_LICENSE` secret** — pin + v2.x (e.g. v2.3.9) for license-free scanning. A failing license gate + masks real scan results; re-run the scan after unblocking and extend + `.gitleaks.toml` allowlists (full-history scans surface deleted test + files — allowlist by path pattern). +- **yamllint applies `line-length` (120) and `new-line-at-end-of-file` + inside `run: |` block scalars and `.github/workflow-templates/` + files** — pre-push check: `awk 'length > 120'` on all + `.yml`/`.yaml`, verify trailing newline via `tail -c 1`. +- **Branch rule `required_review_thread_resolution` blocks merges even + with all checks green** — resolve OwlWatch/DeepSource threads via + GraphQL `resolveReviewThread` before merging; they are merge gates, + not noise. +- See `agents-docs/LESSONS.md` (LESSON-024..026) and + `plans/116-ci-workflow-learnings-2026-08-11.md` for full detail. + ## Skills - Canonical skills live in `.agents/skills/`. diff --git a/agents-docs/LESSONS.md b/agents-docs/LESSONS.md index 0ecc41db..af1bb435 100644 --- a/agents-docs/LESSONS.md +++ b/agents-docs/LESSONS.md @@ -869,3 +869,76 @@ Synchronize the upgrade across the entire Vite ecosystem: - Use `--install-dependencies` flag but expect failures for non-JS tools **Tags**: #codacy #analysis-cli #static-analysis #tooling-gaps + +--- + +## LESSON-024: gitleaks-action v3 requires a paid license + +**Issue**: `Security Scan` workflow failed on every PR with +`🛑 missing gitleaks license` (Plan 115). gitleaks-action v3.0.0 enforced +license validation when its owner-lookup API call failed (self-signed +certificate error reaching api.github.com), and no `GITLEAKS_LICENSE` +secret was configured. + +**Root Cause**: gitleaks-action v3+ requires a paid `GITLEAKS_LICENSE` +secret; v2.x runs license-free for individual users. The failing license +gate also **masked real scan results** — the old allowlist was never +exercised because the scan never ran. + +**Solution**: + +- Pinned `gitleaks/gitleaks-action` to v2.3.9 + (`ff98106e4c7b2bc287b24eaf42907196329070c7`) — log confirms + `No license key is required.` +- After unblocking, re-ran the scan → surfaced **8 false positives** + (test fixtures, historical test files, doc examples) → extended + `.gitleaks.toml` allowlist (`test[_-]?api[_-]?key`, `sk-abc123xyz`, + `BEGIN RSA PRIVATE KEY`, path patterns) +- `workflow_dispatch` scans full git history (`fetch-depth: 0`), so + deleted files still surface — allowlist by path pattern + +**Tags**: #gitleaks #secret-detection #ci-cd #license #allowlist + +--- + +## LESSON-025: yamllint lints block scalars and templates + +**Issue**: `YAML Syntax Validation` CI failed on workflow changes that +parsed as valid YAML (PR #640/#641). + +**Root Cause**: yamllint applies `line-length` (120 max) and +`new-line-at-end-of-file` rules **inside `run: |` block scalars** and +across `.github/workflow-templates/` files, not just top-level structure. + +**Solution**: + +- Break long `run:` script lines with backslash continuation; verify + with `bash -n` on the extracted block +- Ensure trailing newline on every `.yml`/`.yaml`/`.properties.json` + file: `[ -z "$(tail -c 1 file)" ]` +- Pre-push check: `awk 'length > 120 {print NR": "length}'` on all + workflow files + +**Tags**: #yamllint #github-actions #ci-cd #workflow #linting + +--- + +## LESSON-026: Bot review threads block merges even when checks pass + +**Issue**: PRs showed `BLOCKED` with all checks green; the branch rule +`required_review_thread_resolution: true` gates merges on unresolved +review threads, and OwlWatch posts LOW-severity threads on every PR. + +**Root Cause**: `mergeStateStatus` reflects review-thread resolution, +not just CI. Unresolved bot threads (owl-watch, DeepSource) block +merges regardless of severity. + +**Solution**: + +- Before merging, query unresolved threads: GraphQL + `pullRequest.reviewThreads(isResolved == false)` +- Resolve or address each: `resolveReviewThread(input: {threadId})` + mutation +- Treat bot threads as merge gates, not noise + +**Tags**: #review-threads #github #branch-protection #merge #owl-watch diff --git a/agents-docs/lessons.jsonl b/agents-docs/lessons.jsonl index 1544695f..00f27406 100644 --- a/agents-docs/lessons.jsonl +++ b/agents-docs/lessons.jsonl @@ -13,3 +13,6 @@ {"id": "LESSON-013", "date": "2026-04-04", "component": "CI/CD", "title": "CI Hangs Indefinitely Due to BATS Recursion", "severity": "Critical", "tags": ["bats", "recursion", "ci-hang", "timeout", "testing"]} {"id": "LESSON-014", "date": "2026-04-04", "component": "CI/CD", "title": "Shellcheck Warnings vs Errors in CI", "severity": "Medium", "tags": ["shellcheck", "ci", "static-analysis", "warnings", "quality-gate"]} {"id": "LESSON-015", "date": "2026-04-04", "component": "CI/CD", "title": "GitHub API 403 Errors in Generic Templates", "severity": "High", "tags": ["github-api", "permissions", "token", "generic-template", "ci"]} +{"id": "LESSON-024", "date": "2026-08-11", "component": "CI/CD", "title": "gitleaks-action v3 requires a paid license; pin v2.x", "severity": "High", "tags": ["gitleaks", "secret-detection", "license", "allowlist", "github-actions"]} +{"id": "LESSON-025", "date": "2026-08-11", "component": "CI/CD", "title": "yamllint lints block scalars and template files", "severity": "Medium", "tags": ["yamllint", "github-actions", "workflow", "linting"]} +{"id": "LESSON-026", "date": "2026-08-11", "component": "CI/CD", "title": "Bot review threads block merges even when checks pass", "severity": "Medium", "tags": ["review-threads", "branch-protection", "merge", "owl-watch"]} diff --git a/plans/115-gitleaks-license-pre-existing-2026-08-11.md b/plans/115-gitleaks-license-pre-existing-2026-08-11.md index 03df54b3..28d39915 100644 --- a/plans/115-gitleaks-license-pre-existing-2026-08-11.md +++ b/plans/115-gitleaks-license-pre-existing-2026-08-11.md @@ -1,37 +1,79 @@ # Plan 115 — Gitleaks License Failure (Pre-existing Infrastructure Issue) -Date: 2026-08-11 +Date: 2026-08-11 — **RESOLVED** ## Issue -`Security Scan` workflow's **Secret Detection with GitLeaks** step fails on every PR: +`Security Scan` workflow's **Secret Detection with GitLeaks** step fails +on every PR: -``` +```text 🛑 missing gitleaks license. Go grab one at gitleaks.io and store it as a GitHub Secret named GITLEAKS_LICENSE. ``` Root cause chain (from CI logs, run 31520667156): -1. `gitleaks/gitleaks-action@e0c47f4f8be36e29cdc102c57e68cb5cbf0e8d1e` (# v3.0.0) is pinned in `.github/workflows/security-scan.yml` (line 143). -2. The action tries to look up the repo owner (`Get user [d-oit]`) to validate license-free usage. -3. That API call fails with `self-signed certificate` — the runner cannot reach `api.github.com`. -4. gitleaks then **enforces** license validation → no `GITLEAKS_LICENSE` secret is configured → hard failure. +1. `gitleaks/gitleaks-action@e0c47f4f8be36e29cdc102c57e68cb5cbf0e8d1e` + (# v3.0.0) is pinned in `.github/workflows/security-scan.yml` + (line 143). +2. The action tries to look up the repo owner (`Get user [d-oit]`) to + validate license-free usage. +3. That API call fails with `self-signed certificate` — the runner + cannot reach `api.github.com`. +4. gitleaks then **enforces** license validation → no + `GITLEAKS_LICENSE` secret is configured → hard failure. ## Verification -- `security-scan.yml` on `main` is identical to the PR branch (not introduced by any PR). -- `gh secret list` shows **no** `GITLEAKS_LICENSE` secret → pre-existing. -- This check is **not** in the required status checks (only `Codacy Static Code Analysis` is required per branch rules). +- `security-scan.yml` on `main` is identical to the PR branch (not + introduced by any PR). +- `gh secret list` shows **no** `GITLEAKS_LICENSE` secret → + pre-existing. +- This check is **not** in the required status checks (only `Codacy + Static Code Analysis` is required per branch rules). + +## Applied Fix (maintainer decision: pin to v2) + +**PR #643** — pinned `gitleaks/gitleaks-action` to `v2.3.9` (commit +`ff98106e4c7b2bc287b24eaf42907196329070c7`). + +- v2.x runs without a license: log confirms `[d-oit] is an individual + user. No license key is required.` +- TruffleHog fallback step retained for coverage. + +## Follow-up: Allowlist extension (PR #645) + +Pinning to v2 **enabled real scanning** for the first time, which +surfaced **8 false positives** that the old `.gitleaks.toml` allowlist +did not cover (the v3 license gate had masked them): + +- `test-api-key-123456789` in `src/lib/studio/ai-settings.test.ts` +- Historical test files `src/lib/llm/__tests__/*.test.ts` (deleted from + main but scanned via `fetch-depth: 0` full history) +- Doc examples in + `.agents/skills/code-review-assistant/references/security-patterns.md` + (`sk-abc123xyz`, fake RSA key) +- Empty env-var placeholder line in + `.agents/skills/do-web-doc-resolver/SKILL.md` + +`.gitleaks.toml` allowlist extended with `test[_-]?api[_-]?key` regex, +`sk-abc123xyz`, `BEGIN RSA PRIVATE KEY`, and path patterns for the +historical test dir + SKILL.md. -## Options +## Final Verification -| Option | Effort | Notes | -|--------|--------|-------| -| Configure `GITLEAKS_LICENSE` repo secret | Low (needs gitleaks.io account + license) | Cleanest fix; unblocks the step | -| Pin gitleaks-action to v2.x | Low | v2 does not require a license; loses v3 features | -| Add `continue-on-error: true` to the gitleaks step | Trivial | TruffleHog fallback already exists at line 149-155; keeps scan non-blocking | +Manual `security-scan.yml` run on `main` (run 31527596464): +**completed/success** — Shell Script Security Analysis, Secret +Detection, Trivy FS, IaC Scan, Security Scan Summary all green. -## Recommendation +## Lesson for future -Option 1 (configure the secret) is the proper fix. As an interim, Option 3 (non-blocking with the existing TruffleHog fallback) prevents the red check without losing secret scanning coverage. Requires maintainer decision — no autonomous change made here. +- **gitleaks-action v3+ requires a paid `GITLEAKS_LICENSE` secret.** + Pin v2.x for license-free scanning, or configure the secret. +- A failing license gate **masks real scan results** — after any + tooling unblock, re-run the scan to surface genuine findings and + extend allowlists before calling it done. +- GitHub Actions `workflow_dispatch` runs scan the **full git history** + (`fetch-depth: 0`), so deleted files with fixtures still surface as + findings — allowlist by path pattern, not just current-tree paths. diff --git a/plans/116-ci-workflow-learnings-2026-08-11.md b/plans/116-ci-workflow-learnings-2026-08-11.md new file mode 100644 index 00000000..51873a32 --- /dev/null +++ b/plans/116-ci-workflow-learnings-2026-08-11.md @@ -0,0 +1,68 @@ +# Plan 116 — CI/CD Workflow Learnings (2026-08-11 session) + +Date: 2026-08-11 + +## Purpose + +Capture non-obvious CI/CD pitfalls discovered during the PR sweep +(#624–#645) so future sessions avoid repeating them. Companion to the +dual-write entries in `agents-docs/LESSONS.md` (LESSON-024..026) and the +distilled notes in root `AGENTS.md`. + +## Learnings + +### 1. yamllint lints block scalars and templates too + +- `yamllint` applies `line-length` (120 max) and + `new-line-at-end-of-file` **inside `run: |` block scalars** and + workflow template files, not just top-level YAML structure. +- A 158-char `gh pr view ... --jq '...'` line inside a `run:` block + failed CI (PR #640). Broke the line with backslash continuation and + verified with `bash -n`. +- Missing trailing newlines in `.github/workflow-templates/ci.yml` and + `ci.properties.json` failed `YAML Syntax Validation` (PR #641). +- **Checklist**: before pushing workflow/template changes, run + `awk 'length > 120'` on all `.yml`/`.yaml` files and verify trailing + newline (`tail -c 1`). + +### 2. GitHub branch rules require resolved review threads + +- Branch rule `required_review_thread_resolution: true` blocks merges + (BLOCKED) even when **all checks are green**. +- OwlWatch bot posts LOW-severity threads on every PR; unresolved + threads block the merge regardless of severity. +- **Checklist**: before merging, query unresolved threads via GraphQL + (`reviewThreads(isResolved == false)`) and resolve or address each. + +### 3. Pin versions of security tooling deliberately + +- `gitleaks-action` v3+ requires a paid `GITLEAKS_LICENSE` secret; + v2.x runs license-free. See Plan 115. +- A license-gate failure masks real scan results — after unblocking + tooling, re-run the scan to surface genuine findings (8 false + positives appeared only after the v2 pin enabled real scanning). +- `workflow_dispatch` scans full git history (`fetch-depth: 0`), so + deleted test files still surface — allowlist by path pattern, not + just current-tree paths. + +### 4. GitHub merge-state staleness + +- `BLOCKED` can persist minutes after all gates pass (Plan 098). + Verify against the rules endpoints (`/rules/branches/main`), not + `mergeStateStatus` alone. +- Required check is only `Codacy Static Code Analysis`; other failures + (e.g., gitleaks) are non-blocking but show as `UNSTABLE`. + +### 5. Workflow template structure + +- `.github/workflow-templates/*.yml` + `*.properties.json` + (iconName, categories) is the GitHub convention; templates must be + self-contained (inline checkout per job) so they are copy-paste + ready. + +## Files changed + +- `plans/115` — marked RESOLVED (gitleaks v2 pin + allowlist). +- `agents-docs/LESSONS.md` — LESSON-024..026. +- `agents-docs/lessons.jsonl` — matching entries. +- `AGENTS.md` — distilled "Learnings" notes.