Skip to content

feat(agents): require explicit agent config to enable agent per project#897

Merged
aaight merged 3 commits intodevfrom
feature/agent-opt-in-enforcement
Mar 16, 2026
Merged

feat(agents): require explicit agent config to enable agent per project#897
aaight merged 3 commits intodevfrom
feature/agent-opt-in-enforcement

Conversation

@aaight
Copy link
Copy Markdown
Collaborator

@aaight aaight commented Mar 16, 2026

Summary

  • Agents are now opt-in per project — No agent runs unless it has an explicit row in agent_configs for that project
  • New isAgentEnabledForProject() guard — Added with 5s TTL cache to agentConfigsRepository.ts; exempts the debug agent (always enabled)
  • Guard applied at all enforcement points: trigger resolution (isTriggerEnabled, resolveTriggerConfigs, getTriggerParameters, getResolvedTriggerConfig), manual runs (triggerManualRun, triggerRetryRun), and tRPC runs.trigger mutation
  • YAML defaultEnabled changed to false — All triggers in all agent YAML definitions now default to false (defense-in-depth). Zod schema default updated to match
  • Dashboard UIgetProjectTriggersView now returns enabledAgents (have config) + availableAgents (no config). UI shows only enabled agents with an "Available Agents" section featuring "Enable Agent" buttons. Empty state: "No agents enabled. Enable agents below to start processing."
  • CLI agents list — Shows only configured (enabled) agents; displays friendly message when none are enabled
  • Tests — All new guards tested; existing tests updated to mock isAgentEnabledForProject; new tests for disabled-agent rejection in trigger resolution, manual runs, and tRPC endpoint

No backwards compatibility — per card requirements. All existing projects will need admins to explicitly enable agents via Project Settings > Agent Configs.

No DB migration — enforcement is purely in application code.

Trello card: https://trello.com/c/0XSJdemg/416-no-agents-should-be-configured-for-a-project-by-default

Test plan

  • All unit tests pass (5399 tests)
  • TypeScript typechecks clean
  • Lint clean (pre-existing warnings only)
  • isTriggerEnabled returns false without agent config
  • triggerManualRun throws descriptive error when agent not enabled
  • tRPC runs.trigger returns BAD_REQUEST when agent not enabled
  • Dashboard shows empty state when no agents configured
  • Enable Agent button creates bare agent config and moves agent to enabled list

🤖 Generated with Claude Code

🕵️ claude-code · claude-sonnet-4-6 · run details

The new `isAgentEnabledForProject()` guard requires an explicit
`agent_configs` row before any trigger can fire. Integration tests
that called `handle()` and expected a non-null result were failing
because no agent config was seeded.

- Seed `agent_configs` + `agent_trigger_configs` rows in tests that
  expect triggers to fire (trigger-registry, pm-provider-switching,
  github-personas)
- Export `clearAgentEnabledCache()` from agentConfigsRepository for
  test isolation (the 5s TTL cache was causing false negatives when
  tests ran within the TTL window of a prior test that had no config)
- Call `clearAgentEnabledCache()` in `truncateAll()` so every
  `beforeEach` starts with a clean cache alongside a clean DB

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@aaight
Copy link
Copy Markdown
Collaborator Author

aaight commented Mar 16, 2026

CI Failures Resolved

Root Cause

The new isAgentEnabledForProject() guard introduced in this PR requires an explicit agent_configs row before any trigger can fire. Several integration tests were calling trigger handle() methods and expecting non-null results without first seeding an agent config — causing all those tests to return null (agent not enabled).

Additionally, the 5-second TTL in-memory cache for isAgentEnabledForProject() was causing test isolation failures: a prior test that ran without an agent config would cache false, and the next test (which did seed a config) would still see the cached false within the TTL window.

Fixes Applied

  • tests/integration/trigger-registry.test.ts — Added seedAgentConfig + seedTriggerConfig to 4 tests that call handle() and expect a trigger result (implementation, splitting, planning agents)
  • tests/integration/pm-provider-switching.test.ts — Same fix for 3 tests (Trello implementation, JIRA implementation, JIRA planning)
  • tests/integration/github-personas.test.ts — Same fix for 2 tests (respond-to-review and review agents)
  • src/db/repositories/agentConfigsRepository.ts — Exported clearAgentEnabledCache() function for test isolation
  • tests/integration/helpers/db.tstruncateAll() now calls clearAgentEnabledCache() so each beforeEach starts with a clean cache alongside a clean DB

Verification

  • All 296 integration tests passing
  • All 5399 unit tests passing
  • Lint clean (8 pre-existing warnings only)
  • TypeScript typechecks clean
  • Changes pushed to branch

🕵️ claude-code · claude-sonnet-4-6 · run details

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@nhopeatall nhopeatall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

LGTM — Clean, well-structured enforcement of agent opt-in gating. The guard is applied consistently at all entry points (trigger resolution, manual runs, tRPC endpoint), and the defense-in-depth approach of also flipping defaultEnabled to false in all YAML definitions is sound. Tests cover all new paths.

Observations (all verified, no issues)

  • Guard coverage is complete: All trigger handlers flow through checkTriggerEnabledisTriggerEnabledisAgentEnabledForProject. Manual runs check directly. The tRPC runs.trigger endpoint checks before dispatching. The retry endpoint is covered via triggerRetryRuntriggerManualRun which has the guard. The queue worker paths (manual-run, retry-run) ultimately call the same guarded functions.

  • Cache pattern is consistent: isAgentEnabledForProject uses the same 5s TTL Map-based cache as the existing getAgentConfigPrompts and getMaxConcurrency in the same file. clearAgentEnabledCache() is called in truncateAll() for test isolation.

  • UI correctly separates enabled/available: getProjectTriggersView splits agents into enabledAgents (with config row) and availableAgents (without). The backward-compat agents alias pointing to enabledAgents is fine. The "Enable Agent" button creates a bare agent_configs row via the existing create mutation, which has a unique constraint on (projectId, agentType) preventing duplicates.

  • Debug agent exemption: Correctly hardcoded in isAgentEnabledForProject and in the getProjectTriggersView filter logic (always shown as enabled).

  • Schema default change: defaultEnabled: z.boolean().default(false) in the Zod schema ensures any new triggers added to YAML definitions without an explicit defaultEnabled field will default to false — good defense-in-depth.

🕵️ claude-code · claude-opus-4-6 · run details

@aaight aaight merged commit 34adcbe into dev Mar 16, 2026
6 checks passed
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