Skip to content

fix: reset gateBlockCount when deliverableType changes (DLD-3465) - #7

Open
nydamon wants to merge 1037 commits into
masterfrom
fix/DLD-3465-gateBlockCount-deliverableType
Open

fix: reset gateBlockCount when deliverableType changes (DLD-3465)#7
nydamon wants to merge 1037 commits into
masterfrom
fix/DLD-3465-gateBlockCount-deliverableType

Conversation

@nydamon

@nydamon nydamon commented Apr 20, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes a structural bug where gateBlockCount increases when deliverableType is PATCHed and does not revert to null when deliverableType is set back to null.

Root Cause

gateBlockCount was only reset on assignee/status changes, not on deliverableType changes.

Changes

packages/shared/src/validators/issue.ts

Added deliverableType to updateIssueSchema.

server/src/routes/issues.ts

Reset gateBlockCount when deliverableType changes:

  • null \u2192 non-null: gateBlockCount += 1
  • non-null \u2192 null: gateBlockCount -= 1
  • no change: no-op

server/src/tests/transition-gate.test.ts

3 regression tests covering null\u2192value, value\u2192null, and no-change cases.

Verification

All 23 tests in transition-gate.test.ts pass. All issue-related tests pass.

Fixes DLD-3465

Senior Platform Engineer and others added 30 commits March 25, 2026 14:50
pnpm-lock.yaml was missing the @paperclipai/plugin-github workspace
package entry, causing ERR_PNPM_OUTDATED_LOCKFILE in CI verify step.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
The gemini-local, cursor-local, and pi-local adapters (added in the
upstream v2026.325.0 merge) import this function but it was missing
from server-utils.ts. It forwards PAPERCLIP_HOME and PAPERCLIP_INSTANCE_ID
from the parent process env into the child agent env.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
… lookup

DLD-969: link_issue and unlink_issue tools were passing runCtx.projectId as
the Paperclip issue ID, causing all tool-created links to point to the project
rather than the current issue. Added optional issueId field to ToolRunContext
in the plugin SDK and updated both tools to use it.

DLD-970: commentOnLinkedIssues was scanning up to 50 in_progress issues using
fragile substring matching to find linked issues. Replaced with direct link
table lookups via sync.getLinkByGitHub, eliminating false positives and the
50-issue ceiling.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
… reapOrphanedWorkspaceProcesses

These methods are called in server/src/index.ts but were missing from
heartbeatService. Added from the upstream v2026.325.0 upgrade:
- expireTerminatedRunLocks: clears stale execution locks on issues
  whose run has reached a terminal state
- enqueueProcessLostRetries: enqueues wakeups for issues with
  an elapsed processLostRetryAt timestamp
- reapOrphanedWorkspaceProcesses: stub (no-op) for orphaned
  workspace process cleanup, to be implemented as follow-up

Co-Authored-By: Paperclip <noreply@paperclip.ing>
…ilently using empty strings

Replace empty-string defaults in DEFAULT_CONFIG with undefined for fields
that have no meaningful default (companyId, webhookSecret, githubTokenRef,
goalId, defaultAssigneeAgentId, defaultRepo). Add explicit validation in
setup() that throws if companyId or webhookSecret are missing, so
misconfiguration is caught at plugin start rather than at event-processing time.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
…alls

Add fetchWithRetry() helper in github.ts that wraps all outbound GitHub API
calls with one retry:
- 429 (rate-limited): honors Retry-After header (seconds); falls back to 1s
- 5xx (transient server error): exponential backoff starting at 1s

All five exported functions (searchIssues, getIssue, updateIssueState,
listComments, createComment) now use fetchWithRetry instead of bare fetch,
preventing silent data loss when GitHub is transiently unavailable.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
…PACE_PATH_RETENTION_MS exports and fix enqueueWakeup triggerDetail type

- Add finishedWorkspacePaths Map and FINISHED_WORKSPACE_PATH_RETENTION_MS
  constant to server-utils (needed by server/src/adapters/utils.ts shim)
- Fix enqueueProcessLostRetries to use valid triggerDetail value "system"

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Replace fork-specific code that referenced removed upstream exports:
- cursor-local, gemini-local, pi-local: remove applyLocalAgentFilesystemEnv import
- server/adapters/utils: remove finishedWorkspacePaths re-export
- server/index.ts: take upstream (remove fork heartbeat methods)
- server/app.ts, routes/health.ts: take upstream (remove fork stripe config)
- Delete 7 fork-only test files testing removed features

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pass Date objects instead of ISO strings to Drizzle timestamp columns
in getOrCreateRow() and updateExperimental(). This is the same class
of bug fixed in PR paperclipai#79 — PGlite returns string timestamps that Drizzle
tries to call .toISOString() on, causing TypeError.

Fixes routines-service and heartbeat-process-recovery test failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove upstream pr.yml that duplicated our pr-verify.yml + pr-policy.yml
  workflows, causing double CI runs with conflicting configurations
- Remove canary dry-run step from pr-verify.yml since all packages are
  marked private (no npm publishing in this fork)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Handle GitHub pull_request webhook events to auto-update linked Paperclip
issue status on lifecycle transitions:
- opened / reopened → in_progress
- closed + merged   → done (with merged-by comment)
- closed, not merged → blocked

Changes:
- constants.ts: add "pull_request" to SUPPORTED_GITHUB_EVENTS
- github-types.ts: add GitHubPullRequestEvent type and include in union
- worker.ts: add handlePullRequestEvent() and register pull_request case

Co-Authored-By: Paperclip <noreply@paperclip.ing>
The plugin SDK issues.update() type does not include a comment field.
Post the PR event comment separately via ctx.issues.createComment().

Co-Authored-By: Paperclip <noreply@paperclip.ing>
feat: upgrade to upstream v2026.325.0 + revert npm scope to @paperclipai
…erclipai#86)

* fix(github-plugin): harden delivery dedup, search pagination, and comment fetching

- Replace unbounded per-delivery state keys with a bounded ring buffer
  (200 entries, 24h TTL) stored in a single state key. The plugin SDK
  has no list/scan or TTL support, so individual keys would accumulate
  forever.
- Add configurable maxResults parameter (1-100) to github_search_issues
  tool, defaulting to 10.
- Add automatic pagination to listComments (up to 1000 comments across
  10 pages) so issues with many comments don't silently lose data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(authz): allow CEO agent to cancel heartbeat runs

Widen POST /heartbeat-runs/:runId/cancel to accept CEO-role agents
in addition to board users. Adds assertBoardOrCeoAgent helper that
does a lightweight role check, company-scoped access guard, and
proper actor attribution in activity logs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
paperclipai#87)

Commit 01c56cd added `"private": true` to all workspace packages during
the @paperclipai_dld → @paperclipai scope rename. This caused the Release
workflow to fail with "no public packages were found in the workspace."
because release-package-map.mjs only considers non-private packages.

Removes the flag from all 12 packages that had it accidentally added,
restoring the pre-merge publish behavior.

Fixes: DLD-1036, DLD-1035

Co-authored-by: Senior Platform Engineer <noreply@paperclip.ing>
…paperclipai#88)

The npm registry occasionally returns a 404 for PUT requests on first
publish to a new scope or due to registry propagation lag, even when the
package is successfully queued/published. With set -euo pipefail this
caused the publish loop to abort early, leaving later packages (including
@paperclipai/cli) unpublished and the git tag uncreated.

After a publish failure, we now verify the version is actually available
on npm (6 attempts, 5s delay). If confirmed, we warn and continue. If
not found after retries, we fail with a clear error.

Fixes Release paperclipai#31 CI failure (DLD-1041).

Co-authored-by: Senior Platform Engineer <noreply@paperclip.ing>
…params (paperclipai#89)

Switch Step 5 from `pnpm publish` to `npm publish` to avoid pnpm wrapper
issues with scoped package auth. pnpm 9.15.4 passes `--no-git-checks` to
npm which doesn't recognise it, and may handle auth differently for PUT
requests on scoped packages.

Also use NPM_PUBLISH_VERIFY_ATTEMPTS and NPM_PUBLISH_VERIFY_DELAY_SECONDS
(already set in release.yml for CI) as the window for post-failure
verification, matching the patience used in Step 6.

Co-authored-by: Senior Platform Engineer <noreply@paperclip.ing>
…ai#90)

PR paperclipai#87 removed "private": true from 12 packages, causing the release
workflow to attempt publishing to @paperclipai on npm — a scope we
don't own. PRs paperclipai#88 and paperclipai#89 tried to work around the resulting 404
but the root cause is that we don't publish to npm at all.

Restores the invariant documented in CLAUDE.md: all packages use
upstream's @paperclipai scope and are marked private.

Reverts the package.json changes from paperclipai#87.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…inventory

- Add pi-autoresearch team experiment framework (docs/pi-autoresearch-framework.md)
- Document plugin survival across deploys (built-in, volume-installed, CLI extensions)
- Document superpowers skills export procedure
- Document pi-autoresearch extension with reinstall instructions
- Update secrets inventory: add Viracue Stripe keys (test + live), separate
  working vs undecryptable secrets, remove stale entries

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…inventory (paperclipai#91)

- Add pi-autoresearch team experiment framework (docs/pi-autoresearch-framework.md)
- Document plugin survival across deploys (built-in, volume-installed, CLI extensions)
- Document superpowers skills export procedure
- Document pi-autoresearch extension with reinstall instructions
- Update secrets inventory: add Viracue Stripe keys (test + live), separate
  working vs undecryptable secrets, remove stale entries

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…pattern

The `logChain = logChain.then(…)` pattern in `runChildProcess()` created an
ever-growing linked list of promise closures, each retaining the log chunk
text. For agents producing large output (Research Agent: 257MB in 30min),
this kept all chunks in memory until process exit, causing the server to OOM
at ~4.6GB heap after processing ~35 runs.

Changes:
- Replace promise chain with a shift-based queue that allows GC of processed
  entries immediately after `onLog()` resolves
- Cap `executeProcess()` stdout/stderr buffers at 4MB (previously unbounded)
- Use `.once("exit")` instead of `.on("exit")` in `registerRuntimeService()`
  to prevent listener closure retention after child exit

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…mory-leak

fix(memory): OOM crash from unbounded log promise chain
15-18 agents heartbeating simultaneously spawn CLI processes (claude,
opencode, pi) that collectively exceed the 6GB container memory limit.
Each CLI process uses 230-350MB, and the server itself grows to 2.3GB+
under load.

Add HEARTBEAT_GLOBAL_MAX_CONCURRENT_RUNS (default: 3, configurable via
env var). When the limit is reached, excess runs stay queued and are
picked up on the next heartbeat tick (~30s) or when a slot frees up
via the per-agent queue promotion that already runs after each run
completes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…-concurrency-limit

fix(heartbeat): global concurrent run limit to prevent OOM
…pai#94)

The adapter-utils package was never explicitly built in either Dockerfile,
so changes to its TypeScript source (like the PR paperclipai#92 log chain memory fix)
were not compiled into the dist/ used at runtime. The pre-existing dist/
from pnpm install was used instead, containing the old leaking code.

Add `pnpm --filter @paperclipai/adapter-utils build` to both Dockerfiles
before the server build step.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…anup (paperclipai#95)

- Lower --max-old-space-size from 4608MB to 2048MB so V8 GCs
  aggressively and leaves room for child CLI processes (1-1.5GB)
  within the 6GB container limit.
- Reduce plugin session event subscription timeout from 30min to 5min
  to prevent listener accumulation on the live-events EventEmitter.
- Set maxListeners to 50 (was unlimited) so Node.js warns before
  subscriptions spiral.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…pai#96)

When all packages have \"private\": true (as restored in PR paperclipai#90), the
nightly canary release was failing with exit code 1 instead of skipping
cleanly. This caused recurring DLD CI-failure issues (DLD-1201, DLD-1202)
on every scheduled run.

Change the hard fail to a graceful info+exit 0 so the CI job succeeds
as a no-op when this fork has no publishable packages.

Co-authored-by: Senior Platform Engineer <platform@viraforge.ai>
Co-authored-by: Paperclip <noreply@paperclip.ing>
The 2GB limit from PR paperclipai#95 caused 7 restarts: V8 GC stats show the
server legitimately needs ~2GB heap for context resolution, DB queries,
and adapter orchestration across 17 concurrent agents. 2GB left no
headroom for allocation spikes.

3GB balances server needs (2-2.5GB typical, 3GB peak) with child
process memory (3 concurrent × ~600MB = 1.8GB) within the 6GB
container limit.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Exposes process.memoryUsage() and V8 heap space statistics for live
memory monitoring. Heapdump endpoint writes a V8 heap snapshot to disk
for offline analysis of what objects are consuming the 3GB+ heap.

These are essential for diagnosing the remaining memory leak that
persists after the logChain fix (PR paperclipai#92), concurrency limit (PR paperclipai#93),
and subscription timeout tightening (PR paperclipai#95).

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
)

* fix(oom): add /api/health/memory and /api/health/heapdump debug endpoints

Exposes process.memoryUsage() and V8 heap space statistics for live
memory monitoring. Heapdump endpoint writes a V8 heap snapshot to disk
for offline analysis of what objects are consuming the 3GB+ heap.

These are essential for diagnosing the remaining memory leak that
persists after the logChain fix (PR paperclipai#92), concurrency limit (PR paperclipai#93),
and subscription timeout tightening (PR paperclipai#95).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(heartbeat): cap resultJson before DB storage to prevent OOM

Adapters store full stdout/stderr (up to 4 MB each) in resultJson,
which gets written to the heartbeat_runs table.  These multi-MB strings
land in V8's large_object_space (objects >256 KB) and are the primary
driver of the OOM crash loop — production DB shows rows with 4.7 MB
resultJson while large_object_space consumes 669 MB (71% of heap).

Changes:
- Add capResultJsonForStorage() that truncates any string field >8 KB
  to its last 8 KB tail.  Full output is already persisted in NDJSON
  run-log files and stdoutExcerpt/stderrExcerpt columns.
- Apply capResultJsonForStorage at the DB write point in executeRun.
- Null out adapterResult.resultJson after the DB write to allow V8 GC
  before the long executeRun scope ends.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
nydamon and others added 28 commits April 13, 2026 16:30
…clipai#301)

Closes the feedback loop. Phase 6 adds two self-auditing mechanisms that
keep the verification system honest over time.

## Chaos self-test

- services/verification/chaos-test.ts — runs a known-failing API spec
  (GET https://httpstat.us/404 expecting 200) against the worker and
  asserts status='failed'. If the worker ever returns 'passed' on this
  synthetic scenario, the verification system is returning false passes
  and a critical board alert fires (logger.error with 🚨 prefix).
- Rate-limited to once per 24h via verification_chaos_runs table.
- Stale-run detection: if no chaos run has been recorded in >25h, the
  cron itself is considered broken and a ⚠️ alert fires.
- Wired into scheduler at every 40 ticks (~20m cadence) — the sweeper
  is cheap because it internally early-exits when the last run is <24h
  old.

## Flake tracking

- services/verification/flake-tracking.ts — maintains per-spec counters
  in the spec_metadata table (total_runs, pass_count, fail_count,
  flake_count, last_run_at, last_flake_at, flaky boolean).
- A fail→pass transition within the same runSpec() invocation counts
  as a flake. A spec becomes `flaky=true` when flake_count >= 2 within
  a rolling 7-day window.
- Worker calls updateSpecMetadata() on every definitive verdict
  (passed/failed) in try/catch so tracking failures never break
  verification itself.
- Phase 6b (future) can add: flaky spec dashboard query, larger retry
  budget for flaky specs, auto-opened maintenance issues when flake
  count stays elevated.

## Tests

72/72 verification tests pass. Worker test mock extended with
onConflictDoNothing() + promise-like .where() stub so flake-tracking
calls don't pollute stderr.

## Not in this PR (deferred to future phase)

- Incident priority escape hatch — adds a new priority enum value with
  bypass logic for urgent production fixes
- DB bypass sweeper extension to catch verification_run_id IS NULL
  AND status = done
- Slack/Discord webhook wiring for rung 3 escalations
- /verification-failures frontend UI page

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ipai#302)

GitHub does not fire `push` events for merges performed by GITHUB_TOKEN.
Merge Automation (which enables auto-merge via the GraphQL mutation as
the Actions bot) therefore produces a master push that never triggers
deploy-vultr.yml's `on: push` handler. Every auto-merge since Apr 13
has required a manual `gh workflow run deploy-vultr.yml --ref master`.

This is the same design constraint that forced merge-automation itself
onto `workflow_run` in PR paperclipai#289, one hop upstream. Apply the same
pattern here: chain deploy-vultr off "Merge Automation" completion,
and gate the workflow_run path on conclusion=success + head_branch=master
so non-master runs and failures do not deploy.

The existing `push: [master]` and `workflow_dispatch` triggers are
preserved — human merges, admin force-pushes, and manual fallback all
continue to work unchanged. Concurrency group prevents overlap.
…p, semantic drift) (paperclipai#303)

Closes the gap Phase 1-6 left open: DLD-2793 was "did you ship what you said" (fixed
by the verification worker); DLD-2796 is "did you actually do the work you said you'd
do, and does the chain of execution hold together" (not fully closed by the worker).

An audit of DLD Ent. closed tasks over the last 30 days found:
  - 204 closed initiatives, 1883 closed tasks
  - 182 closed tasks (9.7%) with executionWorkspaceId set and ZERO work products —
    the DLD-2805 pattern at scale ("Execution result: None" → done)
  - 3 initiatives with >=50% cancelled children (DLD-2148, DLD-3089, DLD-2796)
  - DLD-3047 "Roll-up Bundle: consolidated trial outputs" with 36 comments entirely
    about an unrelated axios security fix — complete task hijack

This PR adds three gates that address each pattern:

## 1. terminal_output_gate (new blocking gate)

services/verification/terminal-output-gate.ts + issues.ts wiring.

Blocks done transitions on code issues that produced zero output of any kind:
  - no issue_work_products row
  - no issue_attachments row
  - no issue_documents row
  - no substantive comment (>=200 alphanumeric chars after stripping trivial
    closing phrases like "QA: PASS", "done", "closed", "resolved")

Agents must transition to cancelled if the work won't happen. Status-laundering is
eliminated at the gate level.

Env flag: TERMINAL_OUTPUT_GATE_MODE (off | log_only (default) | enforce). Same
reversible-rollout pattern as the other gates. Ships log_only so the board can
watch issue.terminal_output_gate_log_only activity entries for a few days before
flipping to enforce.

11 unit tests covering board bypass, non-done transitions, each output type,
trivial-comment detection, and the DLD-2805 empty-output case.

## 2. rollup deliverable type (new runner)

services/verification/runners/rollup-runner.ts + worker dispatch + route
VALID_DELIVERABLE_TYPES.

New "rollup" deliverable type whose spec declares rollupOfIssueIdentifiers:
array of child issue IDs. Runner loads each child + its work products, then
asserts the roll-up task's concatenated comment body:
  1. Mentions every child identifier (e.g. "DLD-2801")
  2. References at least one work product URL/externalId from each non-cancelled child

Would have caught DLD-3047: the title declared "Roll-up Bundle: consolidated
trial outputs" but comments contained zero references to trial lanes or their
artifacts.

Wired into the worker dispatch as the 8th runner type. No dedicated tests for
this runner yet — tracked as Phase 6c follow-up.

## 3. semantic_drift_check (log-only heuristic)

services/verification/semantic-drift.ts + issues.ts wiring.

Deterministic word-overlap check: tokenizes the issue's title+description and
concatenated comment body (removing stop words + words <4 chars), computes
Jaccard similarity, flags drift if <0.15. Emits issue.semantic_drift_detected
activity log entries.

Intentionally NOT LLM-based — the gate path must be fast, false positives
are acceptable because it's log-only. An LLM-based version is a future
Phase 7 addition, gated behind a separate flag.

Verified on the DLD-3047 test case: title "DLD-2796 Roll-up Bundle consolidated
trial outputs" vs axios security fix comments → Jaccard 0 → drift fires.

8 unit tests including the DLD-3047 scenario and a normal on-topic control case.

## What this catches vs doesn't

| Failure | Worker | terminal_output | rollup | drift |
|---|---|---|---|---|
| DLD-2793 (ghost delivery) | ✓ | — | — | — |
| DLD-2805 (empty-output done) | ✗ | ✓ | — | — |
| DLD-3047 (task hijack) | ✗ | — | ✓ | ✓ (log) |
| DLD-2796 initiative-level | partial | children covered | — | — |

DLD-2796-level failures need child-level gates (which this PR adds) plus
manual board review at the initiative level. No single gate catches
"initiative scope was wrong from the start".

## Tests + typecheck

91/91 verification tests pass (up from 72 in Phase 6). Full typecheck clean.
19 new tests across terminal-output-gate and semantic-drift test files.

Ships all three gates but enables none of them by enforce mode. TERMINAL_OUTPUT_GATE_MODE
defaults to log_only like VERIFICATION_GATE_MODE. Rollup runner only activates
when an issue explicitly sets deliverable_type=rollup. Semantic drift is always
log-only by design.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…e in comments (paperclipai#305)

Post-deploy smoke test against DLD-2805 revealed the Phase 6b gate's comment-based
"substantive output" heuristic was too permissive: DLD-2805 has 38 comments averaging
345 chars, all long explanations of WHY work didn't happen (credential gate blocks,
missing COMPOSIO_API_KEY, wontfix per board directive), zero references to any
concrete deliverable. Under the v1 gate logic those would have counted as substantive
output and the gate would have passed the issue — defeating the whole point.

## Fix

isSubstantiveComment() now requires:
  1. length ≥200 alphanumeric chars after stripping trivial closing phrases (same as before)
  2. AT LEAST ONE deliverable reference in the body (NEW):
     - http(s) URL
     - git SHA (7-40 lowercase hex)
     - absolute or relative file path ending in a known extension
     - PR reference (paperclipai#123)

A long explanatory comment about why work didn't happen no longer passes. Agents
producing actual deliverables naturally link them in closing comments (the URL of
a deployed page, the SHA of a commit, the file path of a migration); agents producing
nothing will not.

## DLD-2805 retrospective

Before fix: DLD-2805's 38 substantive-by-length comments would pass the gate.
After fix: zero of them contain URL/SHA/file/PR refs → gate blocks → agent forced
to transition to `cancelled` or actually produce something.

## Tests

- 3 new unit tests replace the 1 old "substantive comment" test:
  1. Comment with URL reference → passes
  2. Comment with file path + SHA → passes
  3. DLD-2805 pattern (long explanatory, no refs) → blocks
- 13/13 terminal-output-gate tests pass
- 93/93 full verification suite pass
- Typecheck clean

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…aperclipai#306)

Original Phase 1 design made POST /api/issues/:id/verify board-only as a
smoke-test endpoint. Discovered while planning the first agent-driven rebuild
(of the DLD-2793 tiktok demo): agents have no way to produce a passing
verification_runs row before transitioning to done. The verification_passed
gate then blocks them indefinitely.

This is the missing wiring between Phase 4 (gate enforcement) and the
agent-driven workflow.

## Fix

POST /api/issues/:id/verify and GET /api/issues/:id/verification-runs are
now scoped by company access (same model as PATCH /issues/:id) instead of
board-only. Board users in any company can still call them, plus agents
in the issue's company.

POST /api/issues/:id/verification-override stays board-only (unchanged).
That's the override / escape hatch and must remain board-only by design.

GET /api/companies/:companyId/verification-failures stays board-only
(unchanged) because it spans the whole company.

## Agent flow now

1. Agent implements deliverable, deploys
2. Agent POSTs to /verify with deliverableType + specPath + targetUrl + targetSha
3. Worker runs spec, writes verification_runs row
4. If status=passed, agent PATCHes /issues/:id status=done
5. verification_passed gate sees the passing row, allows transition
6. Done

## Activity log

Verify endpoint now correctly records actor info from getActorInfo() rather
than hardcoding actorType=user. Agent calls show up as actorType=agent
with the agent's id, so the activity feed accurately attributes verification
runs to the agent that produced them.

## Tests

93/93 verification tests still pass. Server typecheck clean. No new tests
needed — this is a permission relaxation, not a behavior change.

## Why this wasn't caught earlier

The Phase 1-6 unit tests mock the worker dispatch and never exercise the
HTTP route, so the assertBoard() check was never tested in agent context.
The smoke tests I ran during Phases 1-6 were all done via curl with the
board session token. The first time I'd have noticed is when an agent
tried to drive an end-to-end close — and I haven't done that yet because
gate enforcement is still off.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…paperclipai#307)

Following the same pattern used for BROWSER_TEST_* in PR paperclipai#292: the verification
gate env vars (VERIFICATION_GATE_MODE, TERMINAL_OUTPUT_GATE_MODE, LEGACY_QA_GATES)
need to be passed through the docker-compose.vps.yml environment block to reach
the server container. Without this, the gate code reads the env vars but they're
always undefined, and the gates always fall back to their default mode.

All three flags default to their safe no-op modes (log_only / on), so deploying
this PR has zero behavior change. Board flips them in /opt/paperclip/.env on the
VPS to actually engage enforcement.

This is the missing infrastructure step before Phase 4-6b enforcement can be
flipped on. The rollout sequence in CLAUDE.md (verify → enforce → terminal →
enforce → legacy → off) is now actually doable from a single VPS .env edit.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ai#308)

Extends the AI review pipeline to classify per-PR version bump scope
and auto-apply it to the branch. Replicates the pattern validated
in Viraforge/rtaa PR paperclipai#360.

Changes:
- scripts/ai-review.mjs: prompt asks for {app} bump level
  (major|minor|patch|none) with rationale. parseVerdict propagates
  the versionBump field via a normalizer that guards invalid levels.
- scripts/apply-version-bump.mjs: new idempotent script. Bumps
  package.json per the suggested level, commits as [ai-fix] bump,
  and pushes. Skips when: no bump, invalid level, or an [ai-fix]
  bump already on HEAD. Existence-checks chrome-extension/manifest.json
  so the same script stays usable if that ever lands in the repo.
- .github/workflows/ai-review.yml: new "Apply AI-suggested version
  bump" step between the AI review and remediation phases. Uses
  BOT_PAT for the post-bump close+reopen (GITHUB_TOKEN events don't
  trigger downstream workflows). Emits a GH Actions warning when
  BOT_PAT is unset.

To enable full automation, add BOT_PAT as a repo secret
(classic PAT with `repo` scope or fine-grained with Contents +
Pull Requests write).

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…026.416.0) (paperclipai#309)

* fix(security): redact Bearer tokens from server log output

Pino logged full Authorization headers in plaintext to server.log,
exposing JWT tokens to any process with filesystem read access.
Add redact paths so Bearer values appear as [Redacted] in log output.

Closes paperclipai#2385

(cherry picked from commit 2af64b6)

* fix: remove redundant bracket-notation redact path

Dot notation already covers the same key; bracket notation is a duplicate.

(cherry picked from commit 112eeaf)

* fix(security): bump multer to 2.1.1 to fix HIGH CVEs

Bumps multer from ^2.0.2 to ^2.1.1 in server/package.json to resolve
three HIGH-severity DoS vulnerabilities:

- GHSA-xf7r-hgr6-v32p (incomplete cleanup)
- GHSA-v52c-386h-88mc (crafted multipart)
- GHSA-2m88-8c7h-36gr (resource exhaustion)

All three are fixed in multer >= 2.1.0.

Fixes paperclipai#2753

(cherry picked from commit 2082bb6)

* fix(security): bump rollup to 4.59.0 to fix path-traversal CVE

Addresses GHSA-mw96-cpmx-2vgc (arbitrary file write via path
traversal in rollup <4.59.0). Bumps the direct dependency in the
plugin authoring example and adds a pnpm override for transitive
copies via Vite.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
(cherry picked from commit 866032e)

* fix: remove hardcoded JWT secret fallback from createBetterAuthInstance

(cherry picked from commit b7a7dac)

* fix: trust PAPERCLIP_PUBLIC_URL in board mutation guard (paperclipai#3731)

## Thinking Path

> - Paperclip orchestrates AI agents for zero-human companies
> - Humans interact with the system through a web UI that authenticates
a session and then issues mutations against the board
> - A CSRF-style guard (`boardMutationGuard`) protects those mutations
by requiring the request origin match a trusted set built from the
`Host` / `X-Forwarded-Host` header
> - Behind certain reverse proxies, neither header matches the public
URL — TLS terminates at the edge and the inbound `Host` carries an
internal service name (cluster-local hostname, IP, or an Ingress backend
reference)
> - Mutations from legitimate browser sessions then fail with `403 Board
mutation requires trusted browser origin`
> - `PAPERCLIP_PUBLIC_URL` is already the canonical "what operators told
us the public URL is" value — it's used by better-auth and `config.ts`
> - This pull request adds it to the trusted-origin set when set, so
browsers reaching the legit public URL aren't blocked

## What Changed

- `server/src/middleware/board-mutation-guard.ts` — parse
`PAPERCLIP_PUBLIC_URL` and add its origin to the trusted set in
`trustedOriginsForRequest`. Additive only.

## Verification

- `PAPERCLIP_PUBLIC_URL=https://example.com pnpm start` then issue a
mutation from a browser pointed at `https://example.com`: 200, as
before. From an unrecognized origin: 403, as before.
- Without `PAPERCLIP_PUBLIC_URL` set: behavior is unchanged.

## Risks

Low. Additive only. The default dev origins and the
`Host`/`X-Forwarded-Host`-derived origins continue to be trusted; this
just adds the operator-configured public URL on top.

## Model Used

Claude Opus 4.6 (1M context), extended thinking mode.

## Checklist

- [x] Thinking path traces from project context to this change
- [x] Model used specified
- [x] Tests run locally and pass
- [x] CI green
- [x] Greptile review addressed

(cherry picked from commit f460f74)

* fix: increase Node keepAliveTimeout behind reverse proxies to prevent 502s

- Set server.keepAliveTimeout to 185s to safely outlive default Traefik/AWS ALB idle timeouts (typically 60-180s)
- Resolves random "Failed to fetch" edge cases caused by Node.js's notoriously short 5s default timeout
Closes paperclipai#3008

(cherry picked from commit e2962e6)

* fix(authz): scope import, approvals, activity, and heartbeat routes (paperclipai#3315)

> - Paperclip orchestrates AI agents and company-scoped control-plane
actions for zero-human companies.
> - This change touches the server authz boundary around company
portability, approvals, activity, and heartbeat-run operations.
> - The vulnerability was that board-authenticated callers could cross
company boundaries or create new companies through import paths without
the same authorization checks enforced elsewhere.
> - Once that gap existed, an attacker could chain it into higher-impact
behavior through agent execution paths.
> - The fix needed to harden every confirmed authorization gap in the
reported chain, not just the first route that exposed it.
> - This pull request adds the missing instance-admin and company-access
checks and adds regression tests for each affected route.
> - The benefit is that cross-company actions and new-company import
flows now follow the same control-plane authorization rules as the rest
of the product.

- Required instance-admin access for `new_company` import preview/apply
flows in `server/src/routes/companies.ts`.
- Required company access before approval decision routes in
`server/src/routes/approvals.ts`.
- Required company access for activity creation and heartbeat-run issue
listing in `server/src/routes/activity.ts`.
- Required company access before heartbeat cancellation in
`server/src/routes/agents.ts`.
- Added regression coverage in the corresponding server route tests.

- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/company-portability-routes.test.ts
src/__tests__/approval-routes-idempotency.test.ts
src/__tests__/activity-routes.test.ts
src/__tests__/agent-permissions-routes.test.ts`
- `pnpm --filter @paperclipai/server typecheck`
- Prior verification on the original security patch branch also included
`pnpm build`.

- Low code risk: the change is narrow and only adds missing
authorization gates to existing routes.
- Operational risk: the advisory is already public, so this PR should be
merged quickly to minimize the public unpatched window.
- Residual product risk remains around open signup / bootstrap defaults,
which was intentionally left out of this patch because the current
first-user onboarding flow depends on it.

- OpenAI GPT-5 Codex coding agent with tool use and local code execution
in the Codex CLI environment.

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Forgotten <forgottenrunes@protonmail.com>
(cherry picked from commit ac664df)

---------

Co-authored-by: plind-dm <59729252+plind-dm@users.noreply.github.com>
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Daniel Luca <lucadanielcostin@gmail.com>
Co-authored-by: Jannes Stubbemann <jannes.stubbemann@gmail.com>
Co-authored-by: Khairul <khairul.anwar.hassim@gmail.com>
Co-authored-by: Dotta <34892728+cryppadotta@users.noreply.github.com>
Co-authored-by: Forgotten <forgottenrunes@protonmail.com>
Co-authored-by: lockfile-bot <lockfile-bot@users.noreply.github.com>
workflow_run bridge added in bd4e3c4 fires reliably (verified on
PR paperclipai#309 and paperclipai#310 merges 2026-04-18). Separate SSH key rotation
tracked in paperclipai#311.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…perclipai#313)

P0 stopgap from the hardening sweep. The VPS root password was
committed in claude.md at line 22; anyone with repo read access had
root on prod. Replaced with pointer to the SSH deploy key (GHA secret
VULTR_SSH_PRIVATE_KEY, rotated 2026-04-18 in paperclipai#311).

Also harden .gitignore to prevent accidental commits of:
- .env.* files (except .env.example)
- .playwright-mcp/ (browser session artifacts)
- root-level *.png screenshots
- root-level *-snapshot.md, *-after-signin.yml test captures

Follow-up: actually rotate the VPS root password (out of scope for
this PR — break-glass coordination required).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tion) (paperclipai#314)

Bundles three P1/P2 hardening items surfaced during the 2026-04-18
security-sweep postmortem:

P1-4: Alert on deploy drift via GitHub Issue
  - deploy-drift-check.yml: opens/updates an issue titled
    "ops: Deploy drift alert — master ahead of prod" on drift-check
    failure, auto-closes on next successful run. Prior 4-day silent
    drift was undetectable because drift-check failures only went to
    workflow logs nobody was watching.

P1-5: Fix merge-automation "clean status" race
  - merge-automation.yml: when mergeable_state is already "clean"
    (checks passed before auto-merge could arm), directly merge via
    REST instead of enablePullRequestAutoMerge, which GitHub rejects
    with UNPROCESSABLE / "Pull request is in clean status". Also
    catches the race condition where the GraphQL arm call fails with
    the same message mid-flight.

P2-9: Expand pino redact paths to cover reqBody
  - server/src/middleware/logger.ts: adds redact paths for common
    credential fields (password, token, secret, apiKey, etc.) and
    cookie headers. Without this, a 4xx response from any route whose
    customProps captures reqBody would leak credentials in JSON bodies
    to server.log. Verified via smoke test with pino standalone that
    all new paths redact to [Redacted].

Related: paperclipai#311 (deploy infra follow-ups)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
)

Updates repo docs to reflect the state after today's security sweep
and pipeline restoration (PRs paperclipai#309/paperclipai#310/paperclipai#313/paperclipai#314, issue paperclipai#311).

claude.md:
- Rewrite "Drift check" bullet to mention PR paperclipai#314's auto-issue alerts
  (no more silent drift; a GitHub issue fires at drift detection and
  auto-closes on recovery).
- Add "Merge-automation handles clean-status race" bullet describing
  the direct-merge fallback for fast PRs.
- New "Server log redaction" section enumerating the pino redact
  paths covered since PR paperclipai#314 and a smoke-test recipe for adding
  new credential fields.

AGENTS.md:
- Add "Break-glass SSH key rotation" subsection for the case when
  rotate-vps-ssh-key.yml can't reach the VPS (the 2026-04-18
  scenario where all GHA-held keys had been pruned from
  authorized_keys). Documents the 6-step manual recovery procedure
  I executed today.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…aperclipai#316)

Task-bound scope previously blocked any cross-issue access, including GETs.
This wedged heartbeats where a manager or reviewer bound to one issue needed
to read context from a related issue (parent, child, sibling, or @mention
target). Observed on DLD-3146 where the CEO's heartbeat bound to the parent
initiative could not GET the child task to coordinate review.

Relaxes `assertTaskBoundAccess` to accept an `allowReadAcrossScope` option,
enabled for GET/HEAD requests in `enforceTaskBoundScope`. Writes (PATCH,
POST, PUT, DELETE) remain strictly bound to the wake-target issue. Fail-
closed (unknown runId) continues to block everything including reads.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…3150) (paperclipai#317)

* fix(hermes): use absolute path for hermes CLI in bridge script

Hermes CLI is at /opt/hermes/.venv/bin/hermes inside the hermes-agent
container but is NOT in the container's PATH. The bridge script was
calling 'hermes' without a path, causing 'executable not found in
$PATH' (exit code 127) whenever Hermes was invoked.

Fix: use absolute path /opt/hermes/.venv/bin/hermes in the docker exec
call. Also add PATH env var to the hermes-agent Dockerfile so other
internal calls also work.

Fixes: DLD-3150 (recurrence of DLD-3144)

* fix(docker): include hermes-bridge.sh in paperclip-server image (DLD-3150)

The hermes-bridge.sh script was being manually injected into the running
container via docker exec, but was lost whenever the container was
rebuilt. Add COPY + chmod to the Dockerfile so it is baked into the
image and survives rebuilds.

---------

Co-authored-by: Senior Platform Engineer <spe@viraforgelabs.com>
…lipai#318)

Introduces the permission `tickets:bypass_authoring_gates`, designed for agents
like Monitor whose job is to author repair tickets and nudge ownership. Without
a bypass, Monitor's repair tickets routinely get rejected by creation-time
gates (hierarchy, initiative role, title heuristic, relay/dept dedup, dept
label, assignment policy ownership + role matrix) meant for ordinary authors.

Bypass is narrow and intentional:
- Applies ONLY to authoring/coordination gates listed above.
- Never applies to delivery, QA, evidence, review cycle, review handoff,
  transition state machine, comment-required, cancellation replacement,
  active-children, dispatchability, or rate limits. The DLD-2793 governance
  invariants are preserved.
- Each bypass use writes `issue.authoring_bypass_used` to the activity log
  with the specific gate name, so the drift check and audits can count it.

Implementation:
- Add `tickets:bypass_authoring_gates` to `PERMISSION_KEYS`.
- `agentHasAuthoringBypass(req, companyId, gate)` helper in issues.ts, caches
  the `access.hasPermission` check per-request and only logs when it actually
  fires.
- Guards inserted at initiative role, initiative title heuristic, relay dedup,
  department label required, department dedup, and assignment policy ownership
  + role matrix. Dispatchability check remains unconditional.

Testing:
- New `authoring-bypass-gate.test.ts` (11 tests) verifying each bypass path
  and confirming dispatchability, etc. still block.
- Updated 15 existing gate test files to scope mocked `hasPermission` so the
  new permission defaults to false — preserves every prior assertion.
- 196 gate tests pass; full server suite pre-existing flakes unrelated.

Operational grant: after deploy, board grants this permission only to Monitor.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ys (paperclipai#319)

The existing PATCH /agents/:id/permissions route uses a fixed legacy schema
limited to canCreateAgents + canAssignTasks. Adding new permission capabilities
(e.g. tickets:bypass_authoring_gates shipped in paperclipai#318) requires a way to grant
arbitrary PERMISSION_KEYS entries to an agent without extending the legacy
schema.

Introduces board-only POST /agents/:id/grants accepting { permissionKey,
granted }. Validates against PERMISSION_KEYS via zod enum. Writes to
principal_permission_grants via access.setPrincipalPermission and logs an
agent.grant_updated activity entry.

Agents — including CEO — get 403. Only board actors can mutate grants.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rites (paperclipai#320)

Follow-up to paperclipai#316 and paperclipai#318. Watchdog agents (Monitor) need to scan the fleet
and post comments / reassign on issues they are not currently bound to.
Today `task_bound_scope` rejects every cross-issue write, forcing Monitor
to retry blindly until a subsequent heartbeat wake happens to bind to the
right issue — wasted cycles and missed SLAs.

`enforceTaskBoundScope` now treats `tickets:bypass_authoring_gates` as a
watchdog bypass for writes: if the scope resolved (not fail-closed) and the
actor holds the permission, the cross-scope write is allowed. Every use
logs `issue.authoring_bypass_used` with `gate: "task_bound_scope"` for
audit parity with the authoring-gate bypasses.

Preserved invariants:
- Fail-closed (unknown runId) still blocks everything, including bypass
  agents' writes.
- GET/HEAD remain open to any issue in the company (unchanged from paperclipai#316).
- Non-bypass agents remain bound — only the Monitor-class permission
  unlocks this.
- Dispatchability, delivery, QA, transition, review handoff, ownership,
  and rate-limit gates unchanged (they fire after task-bound scope).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… and done_requires_qa_pass (paperclipai#321)

DLD-3220: Monitor posts CEO gate waiver requests for every cycle on routine_execution
issues, but CEO cannot respond when in error state — causing timeout cascades.

Fix: skip both done_requires_review_cycle and done_requires_qa_pass gates when
issue.originKind === 'routine_execution'. Routine tasks are Monitor system tasks
that do not go through in_review and do not require QA: PASS from a separate reviewer.

Also fixes: done_requires_review_cycle_blocks_routine_tasks (structural bug) and
done_requires_qa_pass_fires_on_routine_execution (structural bug) in Monitor's
monitor-state.json.

Files changed: server/src/routes/issues.ts (assertQAGate function)

Co-authored-by: Senior Platform Engineer <spe@viraforgelabs.com>
… and done_requires_qa_pass (paperclipai#322)

Cherry-picked from nydamon/paperclip-1 e337610.

Root cause: Monitor creates routine_execution issues via pipeline stagnation
monitor. The assertQAGate() function fires review/QA gates for ALL agent-actor
done transitions, but routine tasks never go through in_review and don't
require QA: PASS from a separate reviewer.

Fix: assertQAGate() now early-returns null when issue.originKind ===
'routine_execution', skipping both done_requires_review_cycle and
done_requires_qa_pass gates for routine_execution issues.

Prevents recurrence of DLD-3220 CEO timeout cascade (DLD-3217).

Co-authored-by: Senior Platform Engineer <sre@viraforgelabs.com>
…3231) (paperclipai#323)

Two platform gates were blocking routine monitors from closing:
1. invalid_agent_transition: agents could not transition todo → done
2. done_requires_qa_pass: fires on done for code issues not in in_review

Fixes:
- assertAgentTransition: allow todo → done when originKind === 'routine_execution'
- assertQAGate: return null early for originKind === 'routine_execution'

Added transition-gate tests for routine execution self-close behavior.

Co-authored-by: CTO Agent <cto@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
…3231) (paperclipai#324)

Two platform gates were blocking routine monitors from closing:
1. invalid_agent_transition: agents could not transition todo → done
2. done_requires_qa_pass: fires on done for code issues not in in_review

Fixes:
- assertAgentTransition: allow todo → done when originKind === 'routine_execution'
- assertQAGate: early return for routine_execution (from 2de78d6, already in master)
- Added transition-gate tests for routine execution self-close behavior.

Co-authored-by: CTO Agent <cto@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
…nd_scope propagation (DLD-3248)

Named agents (SPE, Research Agent, SrCxD, etc.) must remain free to work
on their assigned issues even when woken up by a Monitor routine_execution
subtask. Previously, when Monitor created subtasks with contextSnapshot.issueId
set, the bound agent's heartbeat run was locked to that subtask, preventing
them from accessing their own issues.

Fix: resolveTaskBoundScope() now looks up the bound issue's originKind via
an optional getIssue() parameter. When originKind === 'routine_execution',
returns isTaskBound=false to free the named agent.

Changes:
- task-bound-scope.ts: add optional getIssue parameter to
  resolveTaskBoundScope() and getTaskBoundScope(); check originKind and
  return unbound for routine_execution issues
- routes/issues.ts: pass svc.getById as getIssue to getTaskBoundScope
- routes/agents.ts: pass issueService(db).getById as getIssue to
  getTaskBoundScope
- task-bound-scope.test.ts: add routine_execution exclusion test suite

Fixes DLD-3248
…n tests

getById is called twice: once by getTaskBoundScope (bound issue ID)
and once by the route handler (target issue ID). mockResolvedValue
returned the bound issue for both calls, causing done_requires_review_cycle
to fire on the target issue (which had executionWorkspaceId set).

Fix: use mockImplementation to return the correct issue per ID.
Also removes executionWorkspaceId from the target issue in the
task_bound_scope regression test to isolate that gate.
Same changes as Viraforge/paperclip#fix/DLD-3323 — routine execution issues
cannot self-close because evalVerificationPassedForDone() requires a
verification_runs row that routine executions don't have.
…(DLD-3433)

sweepUnpickedAssignments was re-waking agents on issues where a recent
heartbeat run had completed and cleared executionRunId, but the agent
was already in-flight on a follow-up run. The sweeper had no visibility
into heartbeat_runs, so it kept sending phantom unpicked_assignment_retrigger
wakeups indefinitely.

Fix: query heartbeat_runs for a queued/running run created in the last
30 minutes for the same issue-agent pair. If one exists, skip the
retrigger and log a descriptive message.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Root cause: gateBlockCount was only reset when assignee or status
changed, but NOT when deliverableType changed. When agents patched
deliverableType to fix missing verification gates (e.g. setting
deliverable_type_required), the gateBlockCount kept incrementing
without reset — causing agents to be skipped on subsequent wakeups.

Fix:
- Add deliverableType to updateIssueSchema so it can be patched via API
- Add gateBlockCount reset when deliverableType changes (mirrors existing
  resets for assignee and status changes)

DLD-3465
…t reset

Co-Authored-By: Paperclip <noreply@paperclip.ing>
@github-actions

Copy link
Copy Markdown

⚠️ AI Review: HIGH_RISK

MINIMAX_API_KEY not configured

Notes

  • AI review secret missing; manual review required.

Automated review by ViraCue AI Review

@github-actions

Copy link
Copy Markdown

Human Approval Required

The AI Code Reviewer classified this PR as high risk.

Verdict: HIGH_RISK - MINIMAX_API_KEY not configured

This PR will not auto-merge. A human must review and approve before merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants