fix: scope sub_agent_skills unique constraint to tenant/project/agent#2190
fix: scope sub_agent_skills unique constraint to tenant/project/agent#2190nick-inkeep merged 2 commits intomainfrom
Conversation
The unique constraint on sub_agent_skills was only on (sub_agent_id, skill_id), causing cross-tenant collisions when different tenants use sub-agents with the same ID and skill. Updated to include tenant_id, project_id, and agent_id. Co-Authored-By: Claude Opus 4.6 <[email protected]>
🦋 Changeset detectedLatest commit: eaa44d2 The changes in this PR will be included in the next version bump. This PR includes changesets to release 9 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
PR Review Summary
(1) Total Issues | Risk: Low
🟠⚠️ Major (1) 🟠⚠️
🟠 1) manage-schema.ts:293-299 Missing regression test for cross-tenant skill isolation
Issue: This PR fixes a multi-tenancy bug where the unique constraint on sub_agent_skills was scoped only to (sub_agent_id, skill_id) instead of the full tenant hierarchy. However, no regression test is included to prevent this bug from recurring.
Why: A similar fix in PR #2151 included comprehensive scoping tests (subAgentSkills.scoping.test.ts), but those tests only cover same-tenant/same-project/different-agent isolation — not the cross-tenant scenario this PR fixes. Without a specific test for cross-tenant isolation, this bug could reoccur if someone modifies the unique constraint. The production scenario: Tenant A creates sub-agent research-agent with skill documentation-lookup. When Tenant B tries to create the same combination, they get a unique constraint violation that blocks legitimate operations.
Fix: Add a regression test that explicitly verifies different tenants can create the same (sub_agent_id, skill_id) combination. Following the pattern in existing scoping tests:
// packages/agents-core/src/__tests__/data-access/scoping/subAgentSkills.cross-tenant.test.ts
it('should allow different tenants to use same sub_agent_id + skill_id combination', async () => {
const tenant1 = 'tenant-1';
const tenant2 = 'tenant-2';
const sharedSubAgentId = 'shared-subagent';
const sharedSkillId = 'shared-skill';
// Create projects, agents, sub-agents, and skills for both tenants
// ...
// First tenant's insert should succeed
await db.insert(subAgentSkills).values([{
tenantId: tenant1, projectId: project1Id, agentId: agent1Id,
subAgentId: sharedSubAgentId, skillId: sharedSkillId, id: generateId(), index: 0
}]);
// Second tenant's insert should also succeed (not throw unique constraint error)
await expect(
db.insert(subAgentSkills).values([{
tenantId: tenant2, projectId: project2Id, agentId: agent2Id,
subAgentId: sharedSubAgentId, skillId: sharedSkillId, id: generateId(), index: 0
}])
).resolves.not.toThrow();
});Refs:
- subAgentSkills.scoping.test.ts — existing scoping tests (same-tenant only)
- agentFull.components-scoping.test.ts — reference pattern from similar fix in PR #2139
💭 Consider (1) 💭
Inline Comments:
- 💭 Consider:
0010_oval_angel.sql:2Missing trailing newline (POSIX file convention)
💡 APPROVE WITH SUGGESTIONS
Summary: The schema fix is correct and properly scopes the unique constraint to the full tenant/project/agent hierarchy. The migration safely relaxes the constraint (more columns = less restrictive). The data access layer already enforces this scoping, so this brings database-level enforcement in line with application-level behavior.
The one actionable suggestion is adding a regression test for cross-tenant isolation — similar fixes have included such tests, and it would prevent this class of bug from recurring. This is a minor gap that doesn't block the fix, but would strengthen confidence in the change.
Nice catch on this multi-tenancy isolation bug! 🎉
Discarded (5)
| Location | Issue | Reason Discarded |
|---|---|---|
manage-schema.ts |
Schema change is architecturally correct | Positive observation, not an issue |
0010_oval_angel.sql |
Migration safely relaxes constraint | Positive observation, not an issue |
.changeset/cuddly-purple-fish.md |
Changeset correctly uses patch version | Positive observation, not an issue |
credential_references |
Pre-existing constraint (toolId, userId) lacks tenant scoping |
Intentional design — toolId is globally unique via nanoid, userId is a global auth ID |
manage-schema.ts |
No similar issues in other junction tables | Positive observation confirming fix completeness |
Reviewers (6)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
pr-review-tests |
1 | 1 | 0 | 0 | 0 | 0 | 0 |
pr-review-devops |
4 | 0 | 0 | 0 | 1 | 0 | 3 |
pr-review-architecture |
2 | 0 | 0 | 0 | 0 | 0 | 2 |
pr-review-breaking-changes |
1 | 0 | 0 | 0 | 0 | 0 | 1 |
pr-review-security-iam |
1 | 0 | 0 | 0 | 0 | 0 | 1 |
pr-review-standards |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Total | 9 | 1 | 0 | 0 | 1 | 0 | 7 |
…nstraint Addresses review feedback: verifies different tenants can use the same (sub_agent_id, skill_id) combination, and same-tenant duplicates are still blocked. Also fixes trailing newline in migration SQL. Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
Addressed review feedback in eaa44d2:
Both tests pass locally. |
There was a problem hiding this comment.
PR Review Summary
(0) Total Issues | Risk: Low
✅ Prior Feedback Addressed
Both issues from the prior review have been resolved:
| Prior Issue | Status | Resolution |
|---|---|---|
| 🟠 Major: Missing regression test for cross-tenant skill isolation | ✅ Addressed | Added subAgentSkills.cross-tenant.test.ts with 2 comprehensive test cases |
| 💭 Consider: Missing trailing newline in SQL | ✅ Addressed | Newline added to 0010_oval_angel.sql |
Delta Review: New Test File
The new test file (packages/agents-core/src/__tests__/data-access/subAgentSkills.cross-tenant.test.ts) is well-constructed:
Test 1 — Cross-tenant isolation (lines 15-111):
- Creates two separate tenants with distinct projects and agents
- Uses identical
sub_agent_idandskill_idvalues across tenants (the exact scenario that caused the production bug) - Verifies the second tenant's insert succeeds (
.resolves.not.toThrow()) - This would have failed with the old constraint
Test 2 — Same-tenant duplicate prevention (lines 113-173):
- Ensures the fix did not regress the intended uniqueness behavior
- Verifies duplicates within the same tenant/project/agent/subagent are still rejected
Quality observations:
- Follows established patterns from existing scoping tests
- Uses
createTestProjecthelper for proper tenant/project setup - Clear test names describing the behavior being verified
- Tests at the database level without mocking (appropriate for constraint testing)
✅ APPROVE
Summary: All prior review feedback has been addressed. The regression test comprehensively covers the cross-tenant isolation fix while verifying same-tenant uniqueness is preserved. Nice work! 🎉
Reviewers (1)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
pr-review-tests |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Total | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Note: Delta review — only tests reviewer dispatched given the focused scope (1 test file + trailing newline fix).
| name: 'sub_agent_skills_skill_fk', | ||
| }).onDelete('cascade'), | ||
| unique('sub_agent_skills_sub_agent_skill_unique').on(table.subAgentId, table.skillId), | ||
| unique('sub_agent_skills_sub_agent_skill_unique').on( |
There was a problem hiding this comment.
@inkeep Does the data access layer for subagent skills need to have the scopes upated?
|
The data access layer for sub-agent skills ( |
Summary
sub_agent_skillsunique constraint was only on(sub_agent_id, skill_id), causing cross-tenant collisions when different tenants use sub-agents with the same ID and skill combination(tenant_id, project_id, agent_id, sub_agent_id, skill_id)with a Drizzle migrationProblem
Users in a specific project were unable to save skills, getting:
The root cause: the unique constraint
sub_agent_skills_sub_agent_skill_uniquewas scoped only to(sub_agent_id, skill_id)— meaning if any tenant already had a sub-agent with the same ID using the same skill, no other tenant could create that combination.Changes
packages/agents-core/src/db/manage/manage-schema.ts: Updated unique constraint to includetenantId,projectId,agentId,subAgentId,skillIdpackages/agents-core/drizzle/manage/0010_oval_angel.sql: Migration to drop old constraint and create the new tenant-scoped oneTest plan
🤖 Generated with Claude Code