Compare commits

...

11 Commits

Author SHA1 Message Date
devops-engineer 7ccd59e630 Merge branch 'main' into fix/plugin-uninstall-exec-errors
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 4s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 5s
CI / Python Lint & Test (pull_request) Successful in 7s
E2E API Smoke Test / detect-changes (pull_request) Successful in 7s
Harness Replays / detect-changes (pull_request) Successful in 5s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 5s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 4s
Harness Replays / Harness Replays (pull_request) Successful in 2s
E2E Chat / detect-changes (pull_request) Successful in 13s
CI / Detect changes (pull_request) Successful in 14s
sop-checklist / review-refire (pull_request_target) Has been skipped
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 14s
sop-checklist / all-items-acked (pull_request) [info tier:low] acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4
sop-checklist / na-declarations (pull_request) N/A: (none)
qa-review / approved (pull_request_target) Failing after 7s
E2E Chat / E2E Chat (pull_request) Successful in 3s
gate-check-v3 / gate-check (pull_request_target) Successful in 10s
sop-checklist / all-items-acked (pull_request_target) Successful in 7s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 12s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 1s
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 15s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 2s
CI / Canvas (Next.js) (pull_request) Successful in 6s
security-review / approved (pull_request_target) Failing after 11s
CI / Canvas Deploy Status (pull_request) Has been skipped
sop-tier-check / tier-check (pull_request_target) Failing after 16s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 58s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 1m7s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m15s
CI / Platform (Go) (pull_request) Successful in 6m36s
CI / all-required (pull_request) Successful in 3s
qa-review / approved (pull_request_review) Has been skipped
security-review / approved (pull_request_review) Has been skipped
sop-tier-check / tier-check (pull_request_review) Successful in 7s
audit-force-merge / audit (pull_request_target) Successful in 5s
2026-06-06 16:15:21 +00:00
devops-engineer d768d8667b Merge PR #2364 via Gitea merge queue
ci-arm64-advisory / fast-checks (push) Waiting to run
CI / Detect changes (push) Successful in 7s
Block internal-flavored paths / Block forbidden paths (push) Successful in 9s
CI / Python Lint & Test (push) Successful in 7s
E2E API Smoke Test / detect-changes (push) Successful in 7s
E2E Chat / detect-changes (push) Successful in 7s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (push) Successful in 5s
E2E Staging Canvas (Playwright) / detect-changes (push) Successful in 7s
Handlers Postgres Integration / detect-changes (push) Successful in 8s
CI / Platform (Go) (push) Successful in 6s
Secret scan / Scan diff for credential-shaped strings (push) Successful in 4s
CI / Shellcheck (E2E scripts) (push) Successful in 6s
CI / Canvas (Next.js) (push) Successful in 7s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (push) Successful in 8s
E2E API Smoke Test / E2E API Smoke Test (push) Successful in 2s
review-check-tests / review-check.sh regression tests (push) Successful in 7s
E2E Chat / E2E Chat (push) Successful in 5s
CI / Canvas Deploy Status (push) Successful in 3s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (push) Successful in 4s
CI / all-required (push) Successful in 3s
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (push) Successful in 15s
Ops Scripts Tests / Ops scripts (unittest) (push) Successful in 55s
Handlers Postgres Integration / Handlers Postgres Integration (push) Successful in 1m5s
publish-workspace-server-image / build-and-push (push) Successful in 4m5s
publish-workspace-server-image / Production auto-deploy (push) Successful in 2m32s
Serialized merge by gitea-merge-queue after current-main, genuine approvals, and required CI checks were green.
2026-06-06 15:35:31 +00:00
devops-engineer dcf9d3cdc1 Merge branch 'main' into fix/plugin-uninstall-exec-errors
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 5s
CI / Python Lint & Test (pull_request) Successful in 3s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 5s
E2E API Smoke Test / detect-changes (pull_request) Successful in 7s
CI / Detect changes (pull_request) Successful in 11s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 5s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 7s
CI / Canvas (Next.js) (pull_request) Successful in 2s
E2E Chat / detect-changes (pull_request) Successful in 16s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 1s
Harness Replays / detect-changes (pull_request) Successful in 14s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 17s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 12s
qa-review / approved (pull_request_target) Successful in 5s
sop-checklist / review-refire (pull_request_target) Has been skipped
gate-check-v3 / gate-check (pull_request_target) Successful in 7s
CI / Canvas Deploy Status (pull_request) Has been skipped
security-review / approved (pull_request_target) Failing after 4s
Harness Replays / Harness Replays (pull_request) Successful in 1s
E2E Chat / E2E Chat (pull_request) Successful in 2s
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 15s
sop-checklist / all-items-acked (pull_request) [info tier:low] acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / all-items-acked (pull_request_target) Successful in 19s
sop-tier-check / tier-check (pull_request_target) Failing after 18s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 17s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m9s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 1m8s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 1m30s
CI / Platform (Go) (pull_request) Successful in 3m52s
CI / all-required (pull_request) Successful in 2s
2026-06-06 13:30:27 +00:00
Molecule AI Dev Engineer A (Kimi) b1475b1f71 fix(ci): enforce official=true + current-head binding unconditionally in review-check.sh
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 9s
CI / Python Lint & Test (pull_request) Successful in 7s
CI / Detect changes (pull_request) Successful in 8s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 8s
E2E API Smoke Test / detect-changes (pull_request) Successful in 10s
E2E Chat / detect-changes (pull_request) Successful in 10s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 12s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 2s
CI / Canvas (Next.js) (pull_request) Successful in 3s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 9s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 15s
CI / Platform (Go) (pull_request) Successful in 7s
E2E Chat / E2E Chat (pull_request) Successful in 4s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 8s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 6s
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 16s
CI / Canvas Deploy Status (pull_request) Has been skipped
review-check-tests / review-check.sh regression tests (pull_request) Successful in 10s
CI / all-required (pull_request) Successful in 4s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 6s
sop-checklist / review-refire (pull_request_target) Has been skipped
gate-check-v3 / gate-check (pull_request_target) Failing after 13s
sop-checklist / all-items-acked (pull_request) acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4 — body-unfilled: comprehensive-testing, local-postgres-e2
sop-checklist / na-declarations (pull_request) N/A: (none)
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 6s
sop-checklist / all-items-acked (pull_request_target) Successful in 8s
security-review / approved (pull_request_target) Successful in 9s
qa-review / approved (pull_request_target) Successful in 13s
sop-tier-check / tier-check (pull_request_target) Failing after 9s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m9s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Successful in 1m4s
qa-review / approved (pull_request_review) Has been skipped
security-review / approved (pull_request_review) Has been skipped
sop-tier-check / tier-check (pull_request_review) Failing after 11s
audit-force-merge / audit (pull_request_target) Successful in 9s
Addresses CR2 REQUEST_CHANGES on PR #2364:

1. Change select(.official != false) → select(.official == true) so only
   official Gitea reviews count toward qa/security gates.

2. Remove the REVIEW_CHECK_STRICT conditional and always require
   select(.commit_id == ) so stale approvals on old commits are
   rejected.

3. Update test fixture + regression suite:
   - T12: expanded jq-filter test covering official=true and commit-id match
   - T21: stale-head APPROVED review → exit 1
   - T22: missing/non-official APPROVED review → exit 1

All 44 tests pass locally.
2026-06-06 13:21:19 +00:00
Molecule AI Dev Engineer A (Kimi) b2d5f88f98 fix(ci): remove all comment-based approval bypasses from review-check.sh
Issue comments (both generic keywords APPROVED/LGTM/ACCEPTED and agent-
prefix comments [core-qa-agent]/[core-security-agent]) previously
satisfied the qa-review/security-review gate without an official Gitea
review. Both paths are bypasses:

1. Generic keywords: any team member could type 'APPROVED' in a comment.
2. Agent prefix: any team member could type '[core-qa-agent]' in a
   comment — text prefixes are spoofable and lack cryptographic
   verification.

An official Gitea review provides dismissal, stale-review invalidation,
commit_id binding, and an audit trail that issue comments do not.

Changes:
- Removed the entire issue-comments fallback section. Only reviews from
  the Gitea reviews API (state=APPROVED, not dismissed, official, non-
  author) are accepted.
- Updated regression tests:
  T15: agent-prefix comment now fails (exit 1)
  T16: generic-keyword comment still fails (exit 1)
  T18: wrong-team review + right-team comment now fails (exit 1)

Tests: 38 pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 13:21:19 +00:00
devops-engineer 31283a292a Merge PR #2362 via Gitea merge queue
ci-arm64-advisory / fast-checks (push) Waiting to run
Block internal-flavored paths / Block forbidden paths (push) Successful in 4s
CI / Python Lint & Test (push) Successful in 3s
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (push) Has started running
publish-workspace-server-image / build-and-push (push) Has started running
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (push) Successful in 6s
Handlers Postgres Integration / detect-changes (push) Successful in 7s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (push) Successful in 5s
publish-workspace-server-image / Production auto-deploy (push) Blocked by required conditions
E2E Chat / detect-changes (push) Successful in 10s
E2E Staging Canvas (Playwright) / detect-changes (push) Successful in 11s
E2E API Smoke Test / detect-changes (push) Successful in 16s
CI / Detect changes (push) Successful in 18s
E2E Chat / E2E Chat (push) Successful in 3s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (push) Successful in 3s
Secret scan / Scan diff for credential-shaped strings (push) Successful in 6s
CI / Canvas (Next.js) (push) Successful in 2s
CI / Shellcheck (E2E scripts) (push) Successful in 3s
CI / Platform (Go) (push) Successful in 7s
E2E API Smoke Test / E2E API Smoke Test (push) Successful in 7s
CI / Canvas Deploy Status (push) Successful in 3s
CI / all-required (push) Successful in 2s
Ops Scripts Tests / Ops scripts (unittest) (push) Successful in 1m3s
Handlers Postgres Integration / Handlers Postgres Integration (push) Successful in 1m7s
Serialized merge by gitea-merge-queue after current-main, genuine approvals, and required CI checks were green.
2026-06-06 12:50:47 +00:00
Molecule AI Dev Engineer A (Kimi) bc7c45f3d6 fix(security): remove SOP_FAIL_OPEN bypass branches from sop-tier-check.sh (HIGH)
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
CI / Detect changes (pull_request) Successful in 6s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 12s
CI / Python Lint & Test (pull_request) Successful in 11s
E2E API Smoke Test / detect-changes (pull_request) Successful in 5s
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 13s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 5s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 6s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 18s
E2E Chat / detect-changes (pull_request) Successful in 22s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 18s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 7s
qa-review / approved (pull_request_target) Failing after 7s
gate-check-v3 / gate-check (pull_request_target) Successful in 16s
security-review / approved (pull_request_target) Failing after 7s
sop-checklist / review-refire (pull_request_target) Has been skipped
CI / Platform (Go) (pull_request) Successful in 3s
sop-checklist / all-items-acked (pull_request) acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4 — body-unfilled: comprehensive-testing, local-postgres-e2
CI / Canvas (Next.js) (pull_request) Successful in 4s
sop-checklist / na-declarations (pull_request) N/A: (none)
CI / Shellcheck (E2E scripts) (pull_request) Successful in 2s
sop-tier-check / tier-check (pull_request_target) Failing after 8s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 2s
E2E Chat / E2E Chat (pull_request) Successful in 2s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 1s
CI / Canvas Deploy Status (pull_request) Has been skipped
sop-checklist / all-items-acked (pull_request_target) Successful in 18s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 1s
CI / all-required (pull_request) Successful in 3s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m2s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Successful in 1m37s
qa-review / approved (pull_request_review) Has been skipped
security-review / approved (pull_request_review) Has been skipped
sop-tier-check / tier-check (pull_request_review) Failing after 11s
audit-force-merge / audit (pull_request_target) Successful in 6s
The workflow already removed SOP_FAIL_OPEN env (fix/core-ci-fail-closed,
2026-06-05), but the script still carried executable bypass branches that
would exit 0 when the env was set. Remove all of them:

- jq-install failure block
- token whoami failure block
- HEAD_SHA fetch failure block
- /orgs/{o}/teams fetch failure block
- /pulls/{n}/reviews fetch failure block

Every infra fault now fails closed (exit 1) with a loud ::error::,
exactly like a real SOP-6 violation.
2026-06-06 11:05:42 +00:00
devops-engineer bf0db08c7c Merge branch 'main' into fix/plugin-uninstall-exec-errors
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 6s
CI / Detect changes (pull_request) Successful in 8s
CI / Python Lint & Test (pull_request) Successful in 6s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 6s
Harness Replays / detect-changes (pull_request) Successful in 6s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 2s
E2E API Smoke Test / detect-changes (pull_request) Successful in 14s
CI / Canvas (Next.js) (pull_request) Successful in 10s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 17s
E2E Chat / detect-changes (pull_request) Successful in 18s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 4s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 6s
Harness Replays / Harness Replays (pull_request) Successful in 2s
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 14s
qa-review / approved (pull_request_target) Successful in 7s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 16s
gate-check-v3 / gate-check (pull_request_target) Successful in 16s
sop-checklist / review-refire (pull_request_target) Has been skipped
CI / Canvas Deploy Status (pull_request) Has been skipped
security-review / approved (pull_request_target) Failing after 7s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 4s
sop-checklist / all-items-acked (pull_request) [info tier:low] acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4
sop-checklist / na-declarations (pull_request) N/A: (none)
E2E Chat / E2E Chat (pull_request) Successful in 3s
sop-checklist / all-items-acked (pull_request_target) Successful in 9s
sop-tier-check / tier-check (pull_request_target) Failing after 5s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 1m2s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m17s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 1m52s
CI / Platform (Go) (pull_request) Successful in 4m3s
CI / all-required (pull_request) Successful in 1s
2026-06-06 10:50:18 +00:00
devops-engineer e441def8b3 Merge PR #2356 via Gitea merge queue
ci-arm64-advisory / fast-checks (push) Waiting to run
Block internal-flavored paths / Block forbidden paths (push) Successful in 9s
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (push) Successful in 14s
E2E API Smoke Test / detect-changes (push) Successful in 6s
E2E Chat / detect-changes (push) Successful in 6s
E2E Staging Canvas (Playwright) / detect-changes (push) Successful in 9s
Handlers Postgres Integration / detect-changes (push) Successful in 4s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (push) Successful in 4s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (push) Successful in 3s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (push) Successful in 7s
lint-required-workflows-docker-host-pinned / Lint docker-host pin on docker-touching workflows (push) Successful in 10s
CI / Python Lint & Test (push) Successful in 1m8s
CI / Detect changes (push) Successful in 1m12s
E2E API Smoke Test / E2E API Smoke Test (push) Successful in 3s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (push) Successful in 4s
Secret scan / Scan diff for credential-shaped strings (push) Successful in 17s
E2E Chat / E2E Chat (push) Successful in 13s
CI / Platform (Go) (push) Successful in 2s
CI / Shellcheck (E2E scripts) (push) Successful in 1s
CI / Canvas (Next.js) (push) Successful in 17s
lint-continue-on-error-tracking / lint-continue-on-error-tracking (push) Successful in 1m15s
CI / all-required (push) Successful in 6s
Ops Scripts Tests / Ops scripts (unittest) (push) Successful in 1m3s
Lint workflow YAML (Gitea-1.22.6-hostile shapes) / Lint workflow YAML for Gitea-1.22.6-hostile shapes (push) Successful in 1m16s
CI / Canvas Deploy Status (push) Successful in 1s
Handlers Postgres Integration / Handlers Postgres Integration (push) Successful in 3m31s
publish-workspace-server-image / build-and-push (push) Successful in 8m38s
publish-workspace-server-image / Production auto-deploy (push) Successful in 3m11s
Serialized merge by gitea-merge-queue after current-main, genuine approvals, and required CI checks were green.
2026-06-06 10:13:50 +00:00
Molecule AI Dev Engineer A (Kimi) 48b6011e17 fix(2047): pass workspaceID to stripPluginMarkersFromMemory
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
CI / Python Lint & Test (pull_request) Successful in 6s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 8s
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 2s
CI / Detect changes (pull_request) Successful in 8s
E2E API Smoke Test / detect-changes (pull_request) Successful in 9s
Harness Replays / detect-changes (pull_request) Successful in 6s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 5s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 11s
gate-check-v3 / gate-check (pull_request_target) Successful in 5s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 9s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 13s
security-review / approved (pull_request_target) Failing after 5s
qa-review / approved (pull_request_target) Failing after 6s
E2E Chat / detect-changes (pull_request) Successful in 16s
CI / Canvas (Next.js) (pull_request) Successful in 1s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 1s
CI / Canvas Deploy Status (pull_request) Has been skipped
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 2s
E2E Chat / E2E Chat (pull_request) Successful in 2s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 13s
Harness Replays / Harness Replays (pull_request) Successful in 6s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 54s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m32s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 1m49s
CI / Platform (Go) (pull_request) Successful in 3m54s
CI / all-required (pull_request) Successful in 7s
qa-review / approved (pull_request_review) Has been skipped
security-review / approved (pull_request_review) Has been skipped
sop-tier-check / tier-check (pull_request_review) Successful in 5s
sop-checklist / review-refire (pull_request_target) Has been skipped
sop-checklist / all-items-acked (pull_request) [info tier:low] acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-tier-check / tier-check (pull_request_target) Successful in 5s
sop-checklist / all-items-acked (pull_request_target) Successful in 8s
2026-06-05 04:09:20 +00:00
Molecule AI Dev Engineer A (Kimi) cc99d3fff4 fix(plugins): log silently ignored execAsRoot errors during uninstall
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 1s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 3s
CI / Python Lint & Test (pull_request) Successful in 3s
CI / Detect changes (pull_request) Successful in 6s
E2E API Smoke Test / detect-changes (pull_request) Successful in 5s
E2E Chat / detect-changes (pull_request) Successful in 5s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 5s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 6s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 3s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 2s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 4s
gate-check-v3 / gate-check (pull_request_target) Successful in 3s
Harness Replays / detect-changes (pull_request) Successful in 17s
qa-review / approved (pull_request_target) Failing after 4s
sop-checklist / review-refire (pull_request_target) Has been skipped
sop-checklist / all-items-acked (pull_request) acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / all-items-acked (pull_request_target) Successful in 3s
security-review / approved (pull_request_target) Failing after 8s
sop-tier-check / tier-check (pull_request_target) Successful in 4s
CI / Canvas (Next.js) (pull_request) Successful in 1s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 2s
E2E Chat / E2E Chat (pull_request) Successful in 2s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 2s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 58s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Failing after 25s
CI / Platform (Go) (pull_request) Failing after 36s
CI / all-required (pull_request) Has been skipped
Harness Replays / Harness Replays (pull_request) Successful in 2s
CI / Canvas Deploy Status (pull_request) Has been skipped
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Failing after 48s
Plugin uninstall had two sites where execAsRoot errors were discarded:
- Skill directory removal (plugins_install.go:125) — orphaned skill dirs
  if rm -rf failed silently
- CLAUDE.md marker stripping (plugins_install_pipeline.go:326) — stale
  plugin content left in CLAUDE.md if awk script failed

Both now log the error without failing the overall uninstall (best-effort
 cleanup), giving operators visibility into incomplete uninstalls.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-05 03:52:07 +00:00
6 changed files with 95 additions and 109 deletions
+14 -53
View File
@@ -197,19 +197,15 @@ if [ "$HTTP_CODE" != "200" ]; then
exit 1
fi
# Filter: state=APPROVED, not-dismissed, non-author. Optionally strict-mode
# adds commit_id==head.sha (off by default; see header).
# Filter: state=APPROVED, official=true, not-dismissed, non-author,
# commit_id matches current PR head. All conditions are mandatory.
JQ_FILTER='.[]
| select(.state == "APPROVED")
| select(.official == true)
| select(.dismissed != true)
| select(.official != false)
| select(.user.login != $author)'
if [ "${REVIEW_CHECK_STRICT:-}" = "1" ]; then
JQ_FILTER="${JQ_FILTER}
| select(.commit_id == \$head)"
fi
JQ_FILTER="${JQ_FILTER}
| .user.login"
| select(.user.login != $author)
| select(.commit_id == $head)
| .user.login'
REVIEW_CANDIDATES=$(jq -r --arg author "$PR_AUTHOR" --arg head "$PR_HEAD_SHA" "$JQ_FILTER" "$REVIEWS_JSON" | sort -u)
debug "candidate non-author approvers: $(echo "$REVIEW_CANDIDATES" | tr '\n' ' ')"
@@ -241,49 +237,14 @@ if [ -z "$REVIEW_CANDIDATES" ]; then
fi
# --- Fallback/extension (internal#348): check issue comments for agent-approval ---
# core-qa-agent and core-security-agent can approve via issue comments. Always
# include comment candidates, even if the reviews API returned approvals for a
# different team; team membership below is the authoritative filter.
COMMENT_CANDIDATES=""
AGENT_PATTERN=""
case "$TEAM" in
qa) AGENT_PATTERN="\\[core-qa-agent\\]" ;;
security) AGENT_PATTERN="\\[core-security-agent\\]" ;;
esac
HTTP_CODE=$(curl -sS -o "$COMMENTS_JSON" -w '%{http_code}' \
-K "$CURL_AUTH_FILE" "${API}/repos/${OWNER}/${NAME}/issues/${PR_NUMBER}/comments")
debug "GET /issues/${PR_NUMBER}/comments → HTTP ${HTTP_CODE}"
if [ "$HTTP_CODE" = "200" ]; then
# JQ expression: select non-author comments that match either the
# agent-prefix pattern (case-insensitive) OR a generic approval keyword.
JQ_APPROVALS='
.[] |
select(.user.login != $author) |
. as $cmt |
if ($agent_pattern | length) > 0 and ($cmt.body // "" | test($agent_pattern; "i")) then
$cmt.user.login
elif ($cmt.body // "" | test("\\b(APPROVED|LGTM|ACCEPTED)\\b"; "i")) then
$cmt.user.login
else
empty
end
'
COMMENT_CANDIDATES=$(jq -r \
--arg author "$PR_AUTHOR" \
--arg agent_pattern "$AGENT_PATTERN" \
"$JQ_APPROVALS" \
"$COMMENTS_JSON" 2>/dev/null | sort -u)
debug "comment-based approval candidates: $(echo "$COMMENT_CANDIDATES" | tr '\n' ' ')"
if [ -n "$COMMENT_CANDIDATES" ]; then
echo "::notice::${TEAM}-review: found $(echo "$COMMENT_CANDIDATES" | wc -w | xargs) comment-based approval candidate(s) — verifying team membership..."
fi
else
debug "could not fetch issue comments (HTTP ${HTTP_CODE})"
fi
CANDIDATES=$(printf '%s\n%s\n' "$REVIEW_CANDIDATES" "$COMMENT_CANDIDATES" | sed '/^$/d' | sort -u)
# --- COMMENT APPROVAL REMOVED (security hardening) ---
# Previous versions accepted issue comments containing generic approval
# keywords (APPROVED/LGTM/ACCEPTED) or agent prefixes ([core-qa-agent],
# [core-security-agent]) as satisfying the gate. Both paths are bypasses:
# a comment lacks the audit trail, dismissal, stale-review invalidation,
# and commit_id binding that an official Gitea review provides.
# Only APPROVED reviews from the Gitea reviews API count.
CANDIDATES="$REVIEW_CANDIDATES"
if [ -z "${CANDIDATES:-}" ]; then
echo "::error::${TEAM}-review awaiting non-author APPROVE from ${TEAM} team (no candidates from reviews API or issue comments)"
+2 -26
View File
@@ -48,7 +48,6 @@ set -euo pipefail
# workflow-level jq install can fail on runners with network restrictions
# (GitHub releases not reachable from some runner networks — infra#241
# follow-up). This fallback is idempotent — no-op when jq is already on PATH.
# SOP_FAIL_OPEN=1 makes this always exit 0 so CI never blocks on jq absence.
if ! command -v jq >/dev/null 2>&1; then
echo "::notice::jq not found on PATH — attempting install..."
_jq_installed="no"
@@ -67,12 +66,6 @@ if ! command -v jq >/dev/null 2>&1; then
if ! command -v jq >/dev/null 2>&1; then
echo "::error::jq installation failed — apt-get and GitHub binary both failed."
echo "::error::sop-tier-check requires jq for all JSON API parsing."
# SOP_FAIL_OPEN=1 is set in the workflow step's env — makes script always
# exit 0 so CI never blocks. The SOP-6 tier review gate remains enforced.
if [ "${SOP_FAIL_OPEN:-}" = "1" ]; then
echo "::warning::SOP_FAIL_OPEN=1 — exiting 0 so CI does not block."
exit 0
fi
exit 1
fi
fi
@@ -101,15 +94,10 @@ echo "::notice::tier-check start: repo=$OWNER/$NAME pr=$PR_NUMBER author=$PR_AUT
# cause the script to exit prematurely when the token is empty/invalid — the
# if check below handles that case gracefully. Without || true, a 401 from an
# empty/invalid token causes jq to exit 1, triggering set -e and exiting the
# entire script before SOP_FAIL_OPEN can be evaluated (the check is in the jq-
# install block; if jq is already on PATH, that block is skipped entirely).
# entire script before the error can be logged.
WHOAMI=$(curl -sS -H "$AUTH" "${API}/user" | jq -r '.login // ""') || true
if [ -z "$WHOAMI" ]; then
echo "::error::GITEA_TOKEN cannot resolve a user via /api/v1/user — check the token scope and that the secret is wired correctly."
if [ "${SOP_FAIL_OPEN:-}" = "1" ]; then
echo "::warning::SOP_FAIL_OPEN=1 — exiting 0 so CI does not block."
exit 0
fi
exit 1
fi
echo "::notice::token resolves to user: $WHOAMI"
@@ -119,10 +107,6 @@ echo "::notice::token resolves to user: $WHOAMI"
HEAD_SHA=$(curl -sS -H "$AUTH" "${API}/repos/${OWNER}/${NAME}/pulls/${PR_NUMBER}" | jq -r '.head.sha // ""') || true
if [ -z "$HEAD_SHA" ]; then
echo "::error::Failed to fetch PR head SHA — token may be invalid."
if [ "${SOP_FAIL_OPEN:-}" = "1" ]; then
echo "::warning::SOP_FAIL_OPEN=1 — exiting 0 so CI does not block."
exit 0
fi
exit 1
fi
debug "pr-head-sha=$HEAD_SHA"
@@ -215,10 +199,6 @@ if [ "${SOP_DEBUG:-}" = "1" ]; then
fi
if [ "$_HTTP_EXIT" -ne 0 ] || [ "$HTTP_CODE" != "200" ]; then
echo "::error::GET /orgs/${OWNER}/teams failed (curl exit=$_HTTP_EXIT HTTP=$HTTP_CODE) — token may lack read:org scope or be invalid."
if [ "${SOP_FAIL_OPEN:-}" = "1" ]; then
echo "::warning::SOP_FAIL_OPEN=1 — exiting 0 so CI does not block."
exit 0
fi
exit 1
fi
@@ -265,17 +245,13 @@ done
# 5. Read approving reviewers. set +e disables set -e temporarily so that curl
# failures (e.g. empty/invalid token → HTTP 401) do not abort the script before
# SOP_FAIL_OPEN is evaluated. set -e is restored immediately after.
# set -e is restored immediately after.
set +e
REVIEWS=$(curl -sS -H "$AUTH" "${API}/repos/${OWNER}/${NAME}/pulls/${PR_NUMBER}/reviews")
_REVIEWS_EXIT=$?
set -e
if [ $_REVIEWS_EXIT -ne 0 ] || [ -z "$REVIEWS" ]; then
echo "::error::Failed to fetch reviews (curl exit=$_REVIEWS_EXIT) — token may be invalid or unreachable."
if [ "${SOP_FAIL_OPEN:-}" = "1" ]; then
echo "::warning::SOP_FAIL_OPEN=1 — exiting 0 so CI does not block."
exit 0
fi
exit 1
fi
APPROVERS=$(echo "$REVIEWS" | jq -r --arg head_sha "$HEAD_SHA" '[.[] | select(.state=="APPROVED" and .commit_id == $head_sha) | .user.login] | unique | .[]') || true
+17 -6
View File
@@ -109,23 +109,34 @@ class Handler(http.server.BaseHTTPRequestHandler):
return self._json(200, [{
"state": "APPROVED",
"dismissed": True,
"official": True,
"user": {"login": "core-devops"},
"commit_id": "abc1234",
"commit_id": "deadbeef0000111122223333444455556666",
}])
if sc == "T3_reviews_approved_non_author":
return self._json(200, [
{"state": "CHANGES_REQUESTED", "dismissed": False, "user": {"login": "bob"}, "commit_id": "abc1234"},
{"state": "APPROVED", "dismissed": False, "user": {"login": "core-devops"}, "commit_id": "abc1234"},
{"state": "CHANGES_REQUESTED", "dismissed": False, "official": True, "user": {"login": "bob"}, "commit_id": "deadbeef0000111122223333444455556666"},
{"state": "APPROVED", "dismissed": False, "official": True, "user": {"login": "core-devops"}, "commit_id": "deadbeef0000111122223333444455556666"},
])
if sc == "T19_ai_sop_ack_approved":
# ai-sop-ack member submitted APPROVED review — must NOT count
# toward qa-review (team_id=20) or security-review (team_id=21).
return self._json(200, [
{"state": "APPROVED", "dismissed": False, "user": {"login": "ai-reviewer"}, "commit_id": "abc1234"},
{"state": "APPROVED", "dismissed": False, "official": True, "user": {"login": "ai-reviewer"}, "commit_id": "deadbeef0000111122223333444455556666"},
])
# Default: one non-author APPROVED
if sc == "T21_stale_head_approved":
# APPROVED review but on an old commit (stale head) → must be rejected
return self._json(200, [
{"state": "APPROVED", "dismissed": False, "official": True, "user": {"login": "core-devops"}, "commit_id": "oldsha0000000000000000000000000000"},
])
if sc == "T22_missing_official":
# APPROVED review with no official field → must be rejected
return self._json(200, [
{"state": "APPROVED", "dismissed": False, "user": {"login": "core-devops"}, "commit_id": "deadbeef0000111122223333444455556666"},
])
# Default: one non-author APPROVED (current head, official)
return self._json(200, [
{"state": "APPROVED", "dismissed": False, "user": {"login": "core-devops"}, "commit_id": "abc1234"},
{"state": "APPROVED", "dismissed": False, "official": True, "user": {"login": "core-devops"}, "commit_id": "deadbeef0000111122223333444455556666"},
])
# GET /repos/{owner}/{name}/issues/{pr_number}/comments
+53 -19
View File
@@ -14,10 +14,17 @@
# T9 — team membership probe → 403 (token not in team) → script exits 1 (fail closed)
# T10 — CURL_AUTH_FILE created with mode 600 and correct header content
# T11 — bash syntax check (bash -n passes)
# T12 — jq filter: non-author APPROVED → in candidate list; dismissed → excluded
# T12 — jq filter: non-author APPROVED official current-head → in candidate list; dismissed → excluded
# T13 — missing required env GITEA_TOKEN → exits 1 with error
# T14 — non-default-base PR exits 0 without requiring review
# T18wrong-team review candidate does not block right-team comment approval
# T15comment agent-prefix approval → exit 1
# T16 — comment generic keyword approval → exit 1
# T17 — comments with no approval keywords → exit 1
# T18 — wrong-team review + right-team comment → exit 1
# T19 — ai-sop-ack APPROVED review excluded from qa-review gate
# T20 — ai-sop-ack APPROVED review excluded from security-review gate
# T21 — stale-head APPROVED review → exit 1 (commit_id mismatch)
# T22 — missing/non-official APPROVED review → exit 1 (official != true)
#
# Hostile-self-review (per feedback_assert_exact_not_substring):
# this test MUST FAIL if the script is absent. Verified by running
@@ -319,41 +326,50 @@ assert_file_contains "T10b printf header format (CURL_AUTH_FILE content)" "$T10_
assert_file_contains "T10c 'header =' curl-config syntax" "$T10_AUTHFILE" 'header = "Authorization: token '
rm -f "$T10_AUTHFILE"
# T12 — jq filter: non-author APPROVED included, dismissed excluded
# T12 — jq filter: non-author APPROVED official current-head included; dismissed/stale/missing-official excluded
echo
echo "== T12 jq filter =="
# These are tested indirectly via T3 and T6 above, but let's also test
# the jq expression directly.
JQ_FILTER='.[]
| select(.state == "APPROVED")
| select(.official == true)
| select(.dismissed != true)
| select(.user.login != "alice")
| select(.commit_id == $head)
| .user.login'
T12_INPUT='[{"state":"APPROVED","dismissed":false,"user":{"login":"core-devops"}},{"state":"CHANGES_REQUESTED","dismissed":false,"user":{"login":"bob"}},{"state":"APPROVED","dismissed":false,"user":{"login":"alice"}},{"state":"APPROVED","dismissed":true,"user":{"login":"carol"}}]'
T12_INPUT='[{"state":"APPROVED","official":true,"dismissed":false,"commit_id":"deadbeef0000111122223333444455556666","user":{"login":"core-devops"}},{"state":"CHANGES_REQUESTED","official":true,"dismissed":false,"commit_id":"deadbeef0000111122223333444455556666","user":{"login":"bob"}},{"state":"APPROVED","official":true,"dismissed":false,"commit_id":"deadbeef0000111122223333444455556666","user":{"login":"alice"}},{"state":"APPROVED","official":true,"dismissed":true,"commit_id":"deadbeef0000111122223333444455556666","user":{"login":"carol"}},{"state":"APPROVED","official":false,"dismissed":false,"commit_id":"deadbeef0000111122223333444455556666","user":{"login":"dave"}},{"state":"APPROVED","official":true,"dismissed":false,"commit_id":"oldsha0000000000000000000000000000","user":{"login":"eve"}}]'
JQ_CMD=$(command -v jq 2>/dev/null || echo /tmp/jq)
T12_CANDIDATES=$(echo "$T12_INPUT" | "$JQ_CMD" -r "$JQ_FILTER" 2>/dev/null | sort -u)
assert_contains "T12 jq: core-devops (non-author APPROVED) in candidates" "core-devops" "$T12_CANDIDATES"
T12_CANDIDATES=$(echo "$T12_INPUT" | "$JQ_CMD" -r --arg head "deadbeef0000111122223333444455556666" "$JQ_FILTER" 2>/dev/null | sort -u)
assert_contains "T12 jq: core-devops (non-author APPROVED official current-head) in candidates" "core-devops" "$T12_CANDIDATES"
assert_eq "T12 jq: alice (author) NOT in candidates" "" "$(echo "$T12_CANDIDATES" | grep '^alice$' || true)"
assert_eq "T12 jq: carol (dismissed) NOT in candidates" "" "$(echo "$T12_CANDIDATES" | grep '^carol$' || true)"
assert_eq "T12 jq: dave (official=false) NOT in candidates" "" "$(echo "$T12_CANDIDATES" | grep '^dave$' || true)"
assert_eq "T12 jq: eve (stale head) NOT in candidates" "" "$(echo "$T12_CANDIDATES" | grep '^eve$' || true)"
# T15 — comment-based approval via agent prefix pattern → exit 0
# T15 — comment-based approval via agent prefix pattern → exit 1
# SECURITY: agent-prefix comments are also removed. A text prefix in an
# issue comment is spoofable (any team member can type "[core-qa-agent]")
# and lacks the audit trail of an official Gitea review.
echo
echo "== T15 comment agent-prefix approval =="
T15_OUT=$(run_review_check "T15_comments_agent_approval")
T15_RC=$(cat "$FIX_STATE_DIR/last_rc")
assert_eq "T15 exit code 0 (agent-comment approval + team member)" "0" "$T15_RC"
assert_contains "T15 comment fallback notice" "comment-based approval" "$T15_OUT"
assert_contains "T15 core-qa-agent APPROVED" "APPROVED by core-qa-agent" "$T15_OUT"
assert_eq "T15 exit code 1 (agent-prefix comment rejected — not an official review)" "1" "$T15_RC"
assert_contains "T15 no candidates error" "no candidates from reviews API or issue comments" "$T15_OUT"
# T16 — comment-based approval via generic APPROVED keyword → exit 0
# T16 — comment-based approval via generic APPROVED keyword → exit 1
# SECURITY: generic keywords (APPROVED/LGTM/ACCEPTED) must NOT satisfy the
# gate — only official Gitea reviews or agent-prefix comments count. A plain
# comment from a team member is a bypass if it skips the review UI.
echo
echo "== T16 comment generic keyword approval =="
T16_OUT=$(run_review_check "T16_comments_generic_approval")
T16_RC=$(cat "$FIX_STATE_DIR/last_rc")
assert_eq "T16 exit code 0 (generic-approval comment + team member)" "0" "$T16_RC"
assert_contains "T16 comment fallback notice" "comment-based approval" "$T16_OUT"
assert_eq "T16 exit code 1 (generic-approval comment rejected — not an official review)" "1" "$T16_RC"
assert_contains "T16 no candidates error" "no candidates from reviews API or issue comments" "$T16_OUT"
# T17 — no approval keywords in comments → exit 1
echo
@@ -363,16 +379,16 @@ T17_RC=$(cat "$FIX_STATE_DIR/last_rc")
assert_eq "T17 exit code 1 (no candidates from comments)" "1" "$T17_RC"
assert_contains "T17 no candidates error" "no candidates from reviews API or issue comments" "$T17_OUT"
# T18 — a wrong-team PR review candidate must not suppress a right-team
# comment approval. This matches PR #1790, where QA had an APPROVED review
# and security approved via the agent comment convention.
# T18 — wrong-team review + right-team comment → exit 1
# SECURITY: with comment approval fully removed, a wrong-team review plus
# a right-team comment yields NO valid candidates. Only official reviews
# from the target team count.
echo
echo "== T18 review candidate wrong team, comment candidate right team =="
T18_OUT=$(run_review_check "T18_review_wrong_team_comment_right_team")
T18_RC=$(cat "$FIX_STATE_DIR/last_rc")
assert_eq "T18 exit code 0 (comment approval still considered)" "0" "$T18_RC"
assert_contains "T18 comment candidate notice" "comment-based approval" "$T18_OUT"
assert_contains "T18 comment approver accepted" "APPROVED by core-qa-agent" "$T18_OUT"
assert_eq "T18 exit code 1 (comment approval removed — no valid candidates)" "1" "$T18_RC"
assert_contains "T18 none are in team" "none are in team" "$T18_OUT"
# T19 — ai-sop-ack member APPROVED review must NOT count toward qa-review
# or security-review (R1 hardening refinement, msg 1388c76f).
@@ -393,6 +409,24 @@ assert_eq "T20 exit code 1 (ai-sop-ack not in security team)" "1" "$T20_RC"
assert_contains "T20 ai-reviewer excluded from security" "candidates: ai-reviewer" "$T20_OUT"
assert_contains "T20 none are in security team" "none are in team" "$T20_OUT"
# T21 — stale-head APPROVED review must be rejected (commit_id mismatch).
# SECURITY: an approval on an old commit does not cover the current head.
echo
echo "== T21 stale-head APPROVED review rejected =="
T21_OUT=$(run_review_check "T21_stale_head_approved")
T21_RC=$(cat "$FIX_STATE_DIR/last_rc")
assert_eq "T21 exit code 1 (stale-head approval rejected)" "1" "$T21_RC"
assert_contains "T21 no candidates error" "no candidates from reviews API or issue comments" "$T21_OUT"
# T22 — missing/non-official APPROVED review must be rejected.
# SECURITY: only official Gitea reviews count; comments and non-official reviews lack audit trail.
echo
echo "== T22 missing official flag APPROVED review rejected =="
T22_OUT=$(run_review_check "T22_missing_official")
T22_RC=$(cat "$FIX_STATE_DIR/last_rc")
assert_eq "T22 exit code 1 (missing official rejected)" "1" "$T22_RC"
assert_contains "T22 no candidates error" "no candidates from reviews API or issue comments" "$T22_OUT"
echo
echo "------"
echo "PASS=$PASS FAIL=$FAIL"
@@ -161,7 +161,7 @@ func (h *PluginsHandler) uninstallViaDocker(ctx context.Context, c *gin.Context,
// 1. Strip plugin's rule/fragment markers from CLAUDE.md (mirrors
// AgentskillsAdaptor.uninstall lines 184-188). Best-effort: if
// the user edited CLAUDE.md, our marker stays untouched.
h.stripPluginMarkersFromMemory(ctx, containerName, pluginName)
h.stripPluginMarkersFromMemory(ctx, workspaceID, containerName, pluginName)
// 2. Remove copied skill dirs declared in the plugin's plugin.yaml.
for _, skill := range skillNames {
@@ -171,9 +171,11 @@ func (h *PluginsHandler) uninstallViaDocker(ctx context.Context, c *gin.Context,
log.Printf("Plugin uninstall: skipping invalid skill name %q in %s: %v", skill, pluginName, err)
continue
}
_, _ = h.execAsRoot(ctx, containerName, []string{
if _, rmErr := h.execAsRoot(ctx, containerName, []string{
"rm", "-rf", "/configs/skills/" + skill,
})
}); rmErr != nil {
log.Printf("Plugin uninstall: failed to remove skill %s from %s: %v", skill, workspaceID, rmErr)
}
}
// 3. Delete the plugin directory itself (as root to handle file ownership).
@@ -393,7 +393,7 @@ func (h *PluginsHandler) readPluginSkillsFromContainer(ctx context.Context, cont
// `# Plugin: <name> /` — mirrors AgentskillsAdaptor.uninstall's stripping
// logic so install/uninstall are symmetric. Best-effort: silent on read or
// write failure, since the rest of uninstall must still succeed.
func (h *PluginsHandler) stripPluginMarkersFromMemory(ctx context.Context, containerName, pluginName string) {
func (h *PluginsHandler) stripPluginMarkersFromMemory(ctx context.Context, workspaceID, containerName, pluginName string) {
// Use sed via bash -c for atomic in-place delete: drop the marker line
// and the blank line that follows it (install adds a leading blank line
// before the marker via append_to_memory). Three sed passes mirror the
@@ -417,7 +417,9 @@ func (h *PluginsHandler) stripPluginMarkersFromMemory(ctx context.Context, conta
`awk 'BEGIN{skip=0; blanks=0} /^%s/{skip=1; blanks=0; next} skip==1 && /^[[:space:]]*$/{blanks++; if(blanks>=2){skip=0; print; next} next} /^# Plugin: /{if(skip==1)skip=0} skip==1{next} {print}' /configs/CLAUDE.md > /tmp/claude.new && mv /tmp/claude.new /configs/CLAUDE.md`,
regexpEscapeForAwk(marker),
)
_, _ = h.execAsRoot(ctx, containerName, []string{"bash", "-c", script})
if _, awkErr := h.execAsRoot(ctx, containerName, []string{"bash", "-c", script}); awkErr != nil {
log.Printf("Plugin uninstall: failed to strip markers from CLAUDE.md for %s in %s: %v", pluginName, workspaceID, awkErr)
}
}
// regexpEscapeForAwk escapes characters that have special meaning inside an