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
18 changes: 18 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/`.
Expand Down
73 changes: 73 additions & 0 deletions agents-docs/LESSONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions agents-docs/lessons.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -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"]}
78 changes: 60 additions & 18 deletions plans/115-gitleaks-license-pre-existing-2026-08-11.md
Original file line number Diff line number Diff line change
@@ -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.
68 changes: 68 additions & 0 deletions plans/116-ci-workflow-learnings-2026-08-11.md
Original file line number Diff line number Diff line change
@@ -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.
Loading