Skip to content

fix: scope sub_agent_skills unique constraint to tenant/project/agent#2190

Merged
nick-inkeep merged 2 commits intomainfrom
fix/sub-agent-skills-unique-constraint
Feb 20, 2026
Merged

fix: scope sub_agent_skills unique constraint to tenant/project/agent#2190
nick-inkeep merged 2 commits intomainfrom
fix/sub-agent-skills-unique-constraint

Conversation

@nick-inkeep
Copy link
Copy Markdown
Collaborator

Summary

  • Fix multi-tenancy bug where the sub_agent_skills unique 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
  • Updated constraint to (tenant_id, project_id, agent_id, sub_agent_id, skill_id) with a Drizzle migration

Problem

Users in a specific project were unable to save skills, getting:

Failed query: insert into "sub_agent_skills" ... 
params: posthog,...,reverse-lookup-agent,researching-documentation,...

The root cause: the unique constraint sub_agent_skills_sub_agent_skill_unique was 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 include tenantId, projectId, agentId, subAgentId, skillId
  • packages/agents-core/drizzle/manage/0010_oval_angel.sql: Migration to drop old constraint and create the new tenant-scoped one

Test plan

  • All 86 test files pass (1493 tests)
  • Lint clean
  • Migration correctly drops old constraint and creates new one
  • Verify on staging that multiple tenants can save the same sub-agent + skill combination

🤖 Generated with Claude Code

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-bot
Copy link
Copy Markdown

changeset-bot Bot commented Feb 19, 2026

🦋 Changeset detected

Latest commit: eaa44d2

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
@inkeep/agents-core Patch
@inkeep/agents-api Patch
@inkeep/agents-manage-ui Patch
@inkeep/agents-cli Patch
@inkeep/agents-sdk Patch
@inkeep/agents-work-apps Patch
@inkeep/ai-sdk-provider Patch
@inkeep/create-agents Patch
@inkeep/agents-mcp Patch

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

@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agents-api Ready Ready Preview, Comment Feb 20, 2026 0:18am
agents-docs Ready Ready Preview, Comment Feb 20, 2026 0:18am
agents-manage-ui Ready Ready Preview, Comment Feb 20, 2026 0:18am

Request Review

Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

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:


💭 Consider (1) 💭

Inline Comments:

  • 💭 Consider: 0010_oval_angel.sql:2 Missing 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

Comment thread packages/agents-core/drizzle/manage/0010_oval_angel.sql Outdated
@github-actions github-actions Bot deleted a comment from claude Bot Feb 19, 2026
…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]>
@nick-inkeep
Copy link
Copy Markdown
Collaborator Author

Addressed review feedback in eaa44d2:

  1. Cross-tenant regression test addedsubAgentSkills.cross-tenant.test.ts with two test cases:

    • Verifies different tenants can use the same (sub_agent_id, skill_id) combination (the exact scenario that was failing)
    • Verifies same-tenant duplicates are still blocked by the updated constraint
  2. Trailing newline fixed in the migration SQL file.

Both tests pass locally.

Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

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_id and skill_id values 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 createTestProject helper 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).

@github-actions github-actions Bot deleted a comment from claude Bot Feb 20, 2026
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(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@inkeep Does the data access layer for subagent skills need to have the scopes upated?

@inkeep
Copy link
Copy Markdown
Contributor

inkeep Bot commented Feb 20, 2026

The data access layer for sub-agent skills (packages/agents-core/src/data-access/manage/skills.ts) already properly scopes all queries with tenantId, projectId, agentId, and subAgentId. The constraint change only needed to happen at the database schema level to prevent cross-tenant collisions—no DAL changes required.

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.

2 participants