Skip to content

Conversation

@socialyadept
Copy link

@socialyadept socialyadept commented Dec 4, 2025

Summary by CodeRabbit

  • New Features

    • Create GitHub repositories directly from the connector with configurable name, description, visibility, README, .gitignore and license template options.
    • Create GitHub teams with full configuration: privacy settings, maintainers, nested parents, notifications, and repository access management.
  • Chores

    • Updated Go toolchain and core dependencies to newer stable versions for improved compatibility and security.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 4, 2025

Walkthrough

This PR updates Go modules and dependency versions, expands config value handling, and adds authenticated ResourceActions to create GitHub repositories and teams via the connector (registration + handlers, entitlements and grants post-processing).

Changes

Cohort / File(s) Summary
Dependency Updates
go.mod
Bumps Go and many dependencies: baton-sdk v0.4.2→v0.7.10, testify v1.10.0→v1.11.1, otter→v2, gopsutil v3→v4, plus several indirect dependency adjustments and removals.
Config Accessors
pkg/config/conf.gen.go
Formatting changes and expanded GetString to accept string and []byte (returns string for []byte); other accessors unchanged.
Repository Resource Actions
pkg/connector/repository.go
Adds ResourceActions, registers create repository action, and implements handler to parse args, call GitHub API to create a repo, annotate rate limits, build resource representation, generate entitlements, and construct grants.
Team Resource Actions
pkg/connector/team.go
Adds ResourceActions, registers create team action, and implements handler to parse args (privacy, maintainers, repos), call GitHub API to create a team, annotate rate limits, build resource representation, generate entitlements, and construct grants.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Handler as Action Handler
    participant API as GitHub API
    participant Builder as Resource Builder
    participant Ent as Entitlements/Grants

    Client->>Handler: Execute Create Action (args)
    activate Handler
    Handler->>Handler: Validate & Normalize Args
    Handler->>API: Create Repository/Team
    activate API
    API-->>Handler: Created Resource + RateLimit Info
    deactivate API
    Handler->>Builder: Build Resource Object
    activate Builder
    Builder-->>Handler: Resource
    deactivate Builder
    Handler->>Ent: Generate Entitlements
    activate Ent
    Handler->>API: List Members/Collaborators
    API-->>Ent: Members/Collaborators
    Ent-->>Handler: Grants
    deactivate Ent
    Handler-->>Client: Return (success, resource, entitlements, grants)
    deactivate Handler
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through modules, bumped each vine,
I nibble bytes, convert []byte to string fine.
I stitched actions — teams and repos bloom,
With entitlements mapped, grants find room.
A little rabbit patch — deploy and zoom! 🚀

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main changes: adding support for creation actions on team and repository resource types.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch kumail/resource-action/teams/create-delete

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

}

func (o *repositoryResourceType) registerCreateRepositoryAction(ctx context.Context, registry actions.ResourceTypeActionRegistry) error {
return registry.Register(ctx, &v2.ResourceActionSchema{
Copy link
Author

Choose a reason for hiding this comment

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

The list of create repository params is very long, not sure if we should add all of them in the schema or just the most common ones? Right now its adding the most common ones here.

@socialyadept socialyadept changed the title Add create / delete team (group) resource actions Add create / delete / edit team (group) resource actions Dec 8, 2025
@vipulgowda vipulgowda changed the title Add create / delete / edit team (group) resource actions Support for group actions ( team and repository creation) Jan 30, 2026
@vipulgowda vipulgowda force-pushed the kumail/resource-action/teams/create-delete branch from 6513520 to 25ba291 Compare January 30, 2026 07:00
@vipulgowda vipulgowda marked this pull request as ready for review January 30, 2026 08:46
@vipulgowda vipulgowda requested a review from a team January 30, 2026 08:46
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@pkg/connector/repository.go`:
- Around line 654-717: The collaborators and teams fetch only the first page;
update the calls to o.client.Repositories.ListCollaborators and
o.client.Repositories.ListTeams to iterate pages until no more results:
create/reuse ListOptions (collabOpts/teamOpts) with PerPage=maxPageSize and a
for loop that sets Page, calls the list method, appends returned users/teams to
the grants logic on each iteration, breaks when resp.NextPage == 0 (or
len(results)==0), and preserve existing error logging and per-item processing
(variables: collabOpts, users, teamOpts, teams, maxPageSize,
o.client.Repositories.ListCollaborators, o.client.Repositories.ListTeams).
- Around line 601-609: The visibility check currently logs via l.Warn and leaves
newRepo.Visibility unset; replace that silent fallback by validating the value
returned from actions.GetStringArg and handling invalid inputs explicitly:
either set an explicit default (e.g., newRepo.Visibility =
github.Ptr("private")) or, preferably, return an error to the caller (e.g.,
return fmt.Errorf("invalid visibility: %q", visibility)) so the create flow
fails fast; update the branch that currently calls l.Warn (around
actions.GetStringArg / newRepo.Visibility) to perform this explicit defaulting
or error return and ensure the provided visibility is included in the error
message.

In `@pkg/connector/team.go`:
- Around line 625-653: The ListTeamMembersByID call in the loop over
teamAccessLevels can return only the first page, so implement pagination: change
the single ListTeamMembersByID call to a paginated loop that sets
opts.ListOptions.Page (starting at 1) and repeatedly calls
o.client.Teams.ListTeamMembersByID(ctx, createdTeam.GetOrganization().GetID(),
createdTeam.GetID(), opts), processing members into grants (using userResource
and grant.NewGrant as before) and advancing to resp.NextPage until resp.NextPage
== 0; keep PerPage set to maxPageSize and preserve existing error handling and
logging when each page call fails.
🧹 Nitpick comments (2)
pkg/connector/team.go (1)

536-552: Consider returning an error for invalid privacy on nested teams.

When a user requests secret privacy for a nested team, the code logs a warning but silently changes it to closed. This could lead to unexpected results where the user thinks they created a secret team but it's actually closed.

Consider either:

  1. Returning an error explaining the constraint
  2. Including the actual privacy setting in the return values so the caller knows what was applied
Option 1: Return an error for invalid request
 		if isNestedTeam {
 			// Nested teams can only be "closed"
 			if privacy == teamPrivacySecret {
-				l.Warn("github-connector: secret privacy not allowed for nested teams, using closed",
-					zap.String("requested_privacy", privacy),
-				)
+				return nil, nil, fmt.Errorf("nested teams cannot have 'secret' privacy, only 'closed' is allowed")
 			}
 			newTeam.Privacy = github.Ptr(teamPrivacyClosed)
pkg/connector/repository.go (1)

506-551: Hardcoded template lists may need periodic updates.

The gitignore and license template options are hardcoded. While this provides a good UX, these lists may become outdated as GitHub adds new templates.

Consider:

  1. Documenting that these lists are static and may need updates
  2. Alternatively, using GitHub's APIs (/gitignore/templates and /licenses) to populate these dynamically

This is a minor maintenance concern and acceptable for now.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@pkg/connector/repository.go`:
- Around line 483-606: The visibility option currently only supports "public"
and "private" which disallows creating enterprise-only "internal" repos; update
the config options for the "visibility" Field (the StringField options where
Value is "public"/"private") to include an {Value: "internal", DisplayName:
"Internal", Name: "..."} entry and modify handleCreateRepositoryAction to accept
"internal" as a valid visibility value (extend the if check that currently
allows only "public" or "private" to also allow "internal" before setting
newRepo.Visibility). Ensure the default behavior remains unchanged and no other
validation blocks the "internal" value.
- Around line 609-620: The code currently sets newRepo.GitignoreTemplate and
newRepo.LicenseTemplate without ensuring newRepo.AutoInit is true; update the
logic in the repository creation block (symbols: newRepo, AutoInit,
GitignoreTemplate, LicenseTemplate, actions.GetBoolArg, actions.GetStringArg) to
enforce GitHub's requirement: if either gitignore_template or license_template
is provided, ensure newRepo.AutoInit is true—if the user explicitly passed
add_readme=false return an error indicating templates require auto_init=true,
otherwise set newRepo.AutoInit = github.Ptr(true) before assigning the
templates.

In `@pkg/connector/team.go`:
- Around line 450-472: The argument descriptions for the fields named
"maintainers" and "repo_names" are misleading because both are
ResourceIdSliceField (parsed as numeric resource IDs) but the text implies
usernames/full names; update the DisplayName/Description strings for the fields
defined in this block (the "maintainers" and "repo_names" Field entries using
config.Field_ResourceIdSliceField / config.ResourceIdSliceField and
AllowedResourceTypeIds referencing resourceTypeUser.Id and
resourceTypeRepository.Id) to clearly state they expect resource IDs (e.g.,
"List of user resource IDs" and "List of repository resource IDs") so callers
understand these are ID selections not textual usernames/full names.
- Around line 536-548: The privacy branch currently only sets newTeam.Privacy
for allowed values and silently ignores invalid values for non-nested teams; add
an explicit validation for the "privacy" arg when isNestedTeam is false: if
privacy is provided and is not teamPrivacySecret or teamPrivacyClosed, return an
input/argument error (consistent with how invalid parent/maintainer IDs are
handled) instead of letting the GitHub API default it. Locate the block using
actions.GetStringArg(args, "privacy"), isNestedTeam, teamPrivacySecret,
teamPrivacyClosed and newTeam.Privacy and insert the validation+early return for
invalid non-nested privacy values.

Comment on lines +483 to +606
Name: "visibility",
DisplayName: "Visibility",
Description: "The visibility level of the repository",
Field: &config.Field_StringField{
StringField: &config.StringField{
Options: []*config.StringFieldOption{
{Value: "public", DisplayName: "Public", Name: "Anyone on the internet can view this repository"},
{Value: "private", DisplayName: "Private", Name: "You can choose who can see this repository"},
},
DefaultValue: "private",
},
},
},
{
Name: "add_readme",
DisplayName: "Add README.md",
Description: "Add a README.md file to the repository",
Field: &config.Field_BoolField{
BoolField: &config.BoolField{
DefaultValue: true,
},
},
},
{
Name: "gitignore_template",
DisplayName: "Gitignore Template",
Description: "Gitignore template to apply",
Field: &config.Field_StringField{
StringField: &config.StringField{
Options: []*config.StringFieldOption{
{Value: "", DisplayName: "No .gitignore template"},
{Value: "Go", DisplayName: "Go"},
{Value: "Python", DisplayName: "Python"},
{Value: "Node", DisplayName: "Node"},
{Value: "Java", DisplayName: "Java"},
{Value: "Ruby", DisplayName: "Ruby"},
{Value: "Rust", DisplayName: "Rust"},
{Value: "C++", DisplayName: "C++"},
{Value: "C", DisplayName: "C"},
{Value: "Swift", DisplayName: "Swift"},
{Value: "Kotlin", DisplayName: "Kotlin"},
{Value: "Scala", DisplayName: "Scala"},
{Value: "Terraform", DisplayName: "Terraform"},
},
},
},
},
{
Name: "license_template",
DisplayName: "License Template",
Description: "License template to apply",
Field: &config.Field_StringField{
StringField: &config.StringField{
Options: []*config.StringFieldOption{
{Value: "", DisplayName: "No license"},
{Value: "mit", DisplayName: "MIT License"},
{Value: "apache-2.0", DisplayName: "Apache License 2.0"},
{Value: "gpl-3.0", DisplayName: "GNU GPLv3"},
{Value: "gpl-2.0", DisplayName: "GNU GPLv2"},
{Value: "lgpl-3.0", DisplayName: "GNU LGPLv3"},
{Value: "bsd-3-clause", DisplayName: "BSD 3-Clause"},
{Value: "bsd-2-clause", DisplayName: "BSD 2-Clause"},
{Value: "mpl-2.0", DisplayName: "Mozilla Public License 2.0"},
{Value: "unlicense", DisplayName: "The Unlicense"},
{Value: "agpl-3.0", DisplayName: "GNU AGPLv3"},
},
},
},
},
},
ReturnTypes: []*config.Field{
{Name: "success", Field: &config.Field_BoolField{}},
{Name: "resource", Field: &config.Field_ResourceField{}},
{Name: "entitlements", DisplayName: "Entitlements", Field: &config.Field_EntitlementSliceField{
EntitlementSliceField: &config.EntitlementSliceField{},
}},
{Name: "grants", DisplayName: "Grants", Field: &config.Field_GrantSliceField{
GrantSliceField: &config.GrantSliceField{},
}},
},
}, o.handleCreateRepositoryAction)
}

func (o *repositoryResourceType) handleCreateRepositoryAction(ctx context.Context, args *structpb.Struct) (*structpb.Struct, annotations.Annotations, error) {
l := ctxzap.Extract(ctx)

// Extract required arguments using SDK helpers
name, err := actions.RequireStringArg(args, "name")
if err != nil {
return nil, nil, err
}

parentResourceID, err := actions.RequireResourceIDArg(args, "org")
if err != nil {
return nil, nil, err
}

// Get the organization name from the parent resource ID
orgName, err := o.orgCache.GetOrgName(ctx, parentResourceID)
if err != nil {
return nil, nil, fmt.Errorf("failed to get organization name: %w", err)
}

l.Info("github-connector: creating repository via action",
zap.String("repo_name", name),
zap.String("org_name", orgName),
)

// Build the Repository request
newRepo := &github.Repository{
Name: github.Ptr(name),
}

// Extract optional fields using SDK helpers
if description, ok := actions.GetStringArg(args, "description"); ok && description != "" {
newRepo.Description = github.Ptr(description)
}

if visibility, ok := actions.GetStringArg(args, "visibility"); ok && visibility != "" {
if visibility == "public" || visibility == "private" {
newRepo.Visibility = github.Ptr(visibility)
} else {
return nil, nil, fmt.Errorf("invalid visibility: %q (must be \"public\" or \"private\")", visibility)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

GitHub REST API create organization repository visibility values internal

💡 Result:

When creating an organization repository via the GitHub REST API (POST /orgs/{org}/repos), the visibility field accepts:

  • public
  • private
  • internal (only if the org is part of an enterprise account on GitHub Enterprise Cloud or GitHub Enterprise Server ≥ 2.20) [1][2]

Notes:

  • visibility (when provided) overrides the older boolean private field. [1]
  • Historically, internal support required the preview Accept: application/vnd.github.nebula-preview+json; modern clients/docs still mention this in some references. [1][2]

Sources: [1] [2]


🏁 Script executed:

rg "visibility" pkg/connector/repository.go -B2 -A2

Repository: ConductorOne/baton-github

Length of output: 600


🏁 Script executed:

rg "visibility" --type go -B1 -A1

Repository: ConductorOne/baton-github

Length of output: 50381


🏁 Script executed:

grep -n "visibility" pkg/connector/repository.go

Repository: ConductorOne/baton-github

Length of output: 480


Add support for internal visibility for enterprise organizations.

The schema and validation only allow public/private, but GitHub's REST API supports internal for organizations within an enterprise account (GitHub Enterprise Cloud or Server ≥ 2.20). This prevents users from creating internal repositories when using the connector on enterprise instances.

🔧 Proposed update to allow internal visibility
 			{
 				Name:        "visibility",
 				DisplayName: "Visibility",
 				Description: "The visibility level of the repository",
 				Field: &config.Field_StringField{
 					StringField: &config.StringField{
 						Options: []*config.StringFieldOption{
 							{Value: "public", DisplayName: "Public", Name: "Anyone on the internet can view this repository"},
 							{Value: "private", DisplayName: "Private", Name: "You can choose who can see this repository"},
+							{Value: "internal", DisplayName: "Internal", Name: "Visible to organization members"},
 						},
 						DefaultValue: "private",
 					},
 				},
 			},
 	if visibility, ok := actions.GetStringArg(args, "visibility"); ok && visibility != "" {
-		if visibility == "public" || visibility == "private" {
+		if visibility == "public" || visibility == "private" || visibility == "internal" {
 			newRepo.Visibility = github.Ptr(visibility)
 		} else {
-			return nil, nil, fmt.Errorf("invalid visibility: %q (must be \"public\" or \"private\")", visibility)
+			return nil, nil, fmt.Errorf("invalid visibility: %q (must be \"public\", \"private\", or \"internal\")", visibility)
 		}
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Name: "visibility",
DisplayName: "Visibility",
Description: "The visibility level of the repository",
Field: &config.Field_StringField{
StringField: &config.StringField{
Options: []*config.StringFieldOption{
{Value: "public", DisplayName: "Public", Name: "Anyone on the internet can view this repository"},
{Value: "private", DisplayName: "Private", Name: "You can choose who can see this repository"},
},
DefaultValue: "private",
},
},
},
{
Name: "add_readme",
DisplayName: "Add README.md",
Description: "Add a README.md file to the repository",
Field: &config.Field_BoolField{
BoolField: &config.BoolField{
DefaultValue: true,
},
},
},
{
Name: "gitignore_template",
DisplayName: "Gitignore Template",
Description: "Gitignore template to apply",
Field: &config.Field_StringField{
StringField: &config.StringField{
Options: []*config.StringFieldOption{
{Value: "", DisplayName: "No .gitignore template"},
{Value: "Go", DisplayName: "Go"},
{Value: "Python", DisplayName: "Python"},
{Value: "Node", DisplayName: "Node"},
{Value: "Java", DisplayName: "Java"},
{Value: "Ruby", DisplayName: "Ruby"},
{Value: "Rust", DisplayName: "Rust"},
{Value: "C++", DisplayName: "C++"},
{Value: "C", DisplayName: "C"},
{Value: "Swift", DisplayName: "Swift"},
{Value: "Kotlin", DisplayName: "Kotlin"},
{Value: "Scala", DisplayName: "Scala"},
{Value: "Terraform", DisplayName: "Terraform"},
},
},
},
},
{
Name: "license_template",
DisplayName: "License Template",
Description: "License template to apply",
Field: &config.Field_StringField{
StringField: &config.StringField{
Options: []*config.StringFieldOption{
{Value: "", DisplayName: "No license"},
{Value: "mit", DisplayName: "MIT License"},
{Value: "apache-2.0", DisplayName: "Apache License 2.0"},
{Value: "gpl-3.0", DisplayName: "GNU GPLv3"},
{Value: "gpl-2.0", DisplayName: "GNU GPLv2"},
{Value: "lgpl-3.0", DisplayName: "GNU LGPLv3"},
{Value: "bsd-3-clause", DisplayName: "BSD 3-Clause"},
{Value: "bsd-2-clause", DisplayName: "BSD 2-Clause"},
{Value: "mpl-2.0", DisplayName: "Mozilla Public License 2.0"},
{Value: "unlicense", DisplayName: "The Unlicense"},
{Value: "agpl-3.0", DisplayName: "GNU AGPLv3"},
},
},
},
},
},
ReturnTypes: []*config.Field{
{Name: "success", Field: &config.Field_BoolField{}},
{Name: "resource", Field: &config.Field_ResourceField{}},
{Name: "entitlements", DisplayName: "Entitlements", Field: &config.Field_EntitlementSliceField{
EntitlementSliceField: &config.EntitlementSliceField{},
}},
{Name: "grants", DisplayName: "Grants", Field: &config.Field_GrantSliceField{
GrantSliceField: &config.GrantSliceField{},
}},
},
}, o.handleCreateRepositoryAction)
}
func (o *repositoryResourceType) handleCreateRepositoryAction(ctx context.Context, args *structpb.Struct) (*structpb.Struct, annotations.Annotations, error) {
l := ctxzap.Extract(ctx)
// Extract required arguments using SDK helpers
name, err := actions.RequireStringArg(args, "name")
if err != nil {
return nil, nil, err
}
parentResourceID, err := actions.RequireResourceIDArg(args, "org")
if err != nil {
return nil, nil, err
}
// Get the organization name from the parent resource ID
orgName, err := o.orgCache.GetOrgName(ctx, parentResourceID)
if err != nil {
return nil, nil, fmt.Errorf("failed to get organization name: %w", err)
}
l.Info("github-connector: creating repository via action",
zap.String("repo_name", name),
zap.String("org_name", orgName),
)
// Build the Repository request
newRepo := &github.Repository{
Name: github.Ptr(name),
}
// Extract optional fields using SDK helpers
if description, ok := actions.GetStringArg(args, "description"); ok && description != "" {
newRepo.Description = github.Ptr(description)
}
if visibility, ok := actions.GetStringArg(args, "visibility"); ok && visibility != "" {
if visibility == "public" || visibility == "private" {
newRepo.Visibility = github.Ptr(visibility)
} else {
return nil, nil, fmt.Errorf("invalid visibility: %q (must be \"public\" or \"private\")", visibility)
}
{
Name: "visibility",
DisplayName: "Visibility",
Description: "The visibility level of the repository",
Field: &config.Field_StringField{
StringField: &config.StringField{
Options: []*config.StringFieldOption{
{Value: "public", DisplayName: "Public", Name: "Anyone on the internet can view this repository"},
{Value: "private", DisplayName: "Private", Name: "You can choose who can see this repository"},
{Value: "internal", DisplayName: "Internal", Name: "Visible to organization members"},
},
DefaultValue: "private",
},
},
},
Suggested change
Name: "visibility",
DisplayName: "Visibility",
Description: "The visibility level of the repository",
Field: &config.Field_StringField{
StringField: &config.StringField{
Options: []*config.StringFieldOption{
{Value: "public", DisplayName: "Public", Name: "Anyone on the internet can view this repository"},
{Value: "private", DisplayName: "Private", Name: "You can choose who can see this repository"},
},
DefaultValue: "private",
},
},
},
{
Name: "add_readme",
DisplayName: "Add README.md",
Description: "Add a README.md file to the repository",
Field: &config.Field_BoolField{
BoolField: &config.BoolField{
DefaultValue: true,
},
},
},
{
Name: "gitignore_template",
DisplayName: "Gitignore Template",
Description: "Gitignore template to apply",
Field: &config.Field_StringField{
StringField: &config.StringField{
Options: []*config.StringFieldOption{
{Value: "", DisplayName: "No .gitignore template"},
{Value: "Go", DisplayName: "Go"},
{Value: "Python", DisplayName: "Python"},
{Value: "Node", DisplayName: "Node"},
{Value: "Java", DisplayName: "Java"},
{Value: "Ruby", DisplayName: "Ruby"},
{Value: "Rust", DisplayName: "Rust"},
{Value: "C++", DisplayName: "C++"},
{Value: "C", DisplayName: "C"},
{Value: "Swift", DisplayName: "Swift"},
{Value: "Kotlin", DisplayName: "Kotlin"},
{Value: "Scala", DisplayName: "Scala"},
{Value: "Terraform", DisplayName: "Terraform"},
},
},
},
},
{
Name: "license_template",
DisplayName: "License Template",
Description: "License template to apply",
Field: &config.Field_StringField{
StringField: &config.StringField{
Options: []*config.StringFieldOption{
{Value: "", DisplayName: "No license"},
{Value: "mit", DisplayName: "MIT License"},
{Value: "apache-2.0", DisplayName: "Apache License 2.0"},
{Value: "gpl-3.0", DisplayName: "GNU GPLv3"},
{Value: "gpl-2.0", DisplayName: "GNU GPLv2"},
{Value: "lgpl-3.0", DisplayName: "GNU LGPLv3"},
{Value: "bsd-3-clause", DisplayName: "BSD 3-Clause"},
{Value: "bsd-2-clause", DisplayName: "BSD 2-Clause"},
{Value: "mpl-2.0", DisplayName: "Mozilla Public License 2.0"},
{Value: "unlicense", DisplayName: "The Unlicense"},
{Value: "agpl-3.0", DisplayName: "GNU AGPLv3"},
},
},
},
},
},
ReturnTypes: []*config.Field{
{Name: "success", Field: &config.Field_BoolField{}},
{Name: "resource", Field: &config.Field_ResourceField{}},
{Name: "entitlements", DisplayName: "Entitlements", Field: &config.Field_EntitlementSliceField{
EntitlementSliceField: &config.EntitlementSliceField{},
}},
{Name: "grants", DisplayName: "Grants", Field: &config.Field_GrantSliceField{
GrantSliceField: &config.GrantSliceField{},
}},
},
}, o.handleCreateRepositoryAction)
}
func (o *repositoryResourceType) handleCreateRepositoryAction(ctx context.Context, args *structpb.Struct) (*structpb.Struct, annotations.Annotations, error) {
l := ctxzap.Extract(ctx)
// Extract required arguments using SDK helpers
name, err := actions.RequireStringArg(args, "name")
if err != nil {
return nil, nil, err
}
parentResourceID, err := actions.RequireResourceIDArg(args, "org")
if err != nil {
return nil, nil, err
}
// Get the organization name from the parent resource ID
orgName, err := o.orgCache.GetOrgName(ctx, parentResourceID)
if err != nil {
return nil, nil, fmt.Errorf("failed to get organization name: %w", err)
}
l.Info("github-connector: creating repository via action",
zap.String("repo_name", name),
zap.String("org_name", orgName),
)
// Build the Repository request
newRepo := &github.Repository{
Name: github.Ptr(name),
}
// Extract optional fields using SDK helpers
if description, ok := actions.GetStringArg(args, "description"); ok && description != "" {
newRepo.Description = github.Ptr(description)
}
if visibility, ok := actions.GetStringArg(args, "visibility"); ok && visibility != "" {
if visibility == "public" || visibility == "private" {
newRepo.Visibility = github.Ptr(visibility)
} else {
return nil, nil, fmt.Errorf("invalid visibility: %q (must be \"public\" or \"private\")", visibility)
}
if visibility, ok := actions.GetStringArg(args, "visibility"); ok && visibility != "" {
if visibility == "public" || visibility == "private" || visibility == "internal" {
newRepo.Visibility = github.Ptr(visibility)
} else {
return nil, nil, fmt.Errorf("invalid visibility: %q (must be \"public\", \"private\", or \"internal\")", visibility)
}
}
🤖 Prompt for AI Agents
In `@pkg/connector/repository.go` around lines 483 - 606, The visibility option
currently only supports "public" and "private" which disallows creating
enterprise-only "internal" repos; update the config options for the "visibility"
Field (the StringField options where Value is "public"/"private") to include an
{Value: "internal", DisplayName: "Internal", Name: "..."} entry and modify
handleCreateRepositoryAction to accept "internal" as a valid visibility value
(extend the if check that currently allows only "public" or "private" to also
allow "internal" before setting newRepo.Visibility). Ensure the default behavior
remains unchanged and no other validation blocks the "internal" value.

Comment on lines +609 to +620
// add_readme maps to AutoInit in GitHub API
if addReadme, ok := actions.GetBoolArg(args, "add_readme"); ok {
newRepo.AutoInit = github.Ptr(addReadme)
}

if gitignoreTemplate, ok := actions.GetStringArg(args, "gitignore_template"); ok && gitignoreTemplate != "" {
newRepo.GitignoreTemplate = github.Ptr(gitignoreTemplate)
}

if licenseTemplate, ok := actions.GetStringArg(args, "license_template"); ok && licenseTemplate != "" {
newRepo.LicenseTemplate = github.Ptr(licenseTemplate)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

GitHub API create repository gitignore_template license_template auto_init requirement

💡 Result:

For the GitHub REST API “create repository” endpoints (POST /user/repos and POST /orgs/{org}/repos), the only required field is:

  • name (string) — repository name. [1]

The fields you mentioned are optional request-body parameters:

  • auto_init (boolean) — initialize the repo with an initial commit (minimal README). Default false. [1]
  • gitignore_template (string) — the .gitignore template name to apply at creation time. The valid names are the ones returned by GET /gitignore/templates. [1][2]
  • license_template (string) — the license keyword to apply at creation time. Valid keywords come from GET /licenses (commonly used licenses). [1][3]

Practical requirement/behavior:

  • To initialize the repository at creation time (and thereby have GitHub apply things like a .gitignore template during creation), set auto_init: true. GitHub describes gitignore_template as an option used when initializing a repo via the API. [4][1]

Sources: [1] GitHub Docs (REST “Repos” → “Create a repository for the authenticated user”, params include auto_init, gitignore_template, license_template). [2] GitHub Docs (REST “Gitignore” → “Get all gitignore templates”). [3] GitHub Docs (REST “Licenses” → “Get all commonly used licenses”). [4] GitHub Developer Guide (API change note describing initializing repos via API and using gitignore_template).


Enforce auto_init=true when gitignore_template or license_template is supplied.

GitHub's API applies gitignore_template and license_template only when auto_init=true. The current code allows these templates to be set independently without ensuring auto_init is enabled, which will result in the templates being silently ignored when the repo is created. Add validation to either force auto_init=true when templates are supplied, or return an error if templates are provided without auto_init=true.

🔧 Suggested guard
 	// add_readme maps to AutoInit in GitHub API
-	if addReadme, ok := actions.GetBoolArg(args, "add_readme"); ok {
-		newRepo.AutoInit = github.Ptr(addReadme)
-	}
+	autoInit := true
+	if addReadme, ok := actions.GetBoolArg(args, "add_readme"); ok {
+		autoInit = addReadme
+	}
+	newRepo.AutoInit = github.Ptr(autoInit)
@@
 	if licenseTemplate, ok := actions.GetStringArg(args, "license_template"); ok && licenseTemplate != "" {
 		newRepo.LicenseTemplate = github.Ptr(licenseTemplate)
 	}
+
+	if (newRepo.GitignoreTemplate != nil || newRepo.LicenseTemplate != nil) && !autoInit {
+		return nil, nil, fmt.Errorf("gitignore_template/license_template require add_readme=true")
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// add_readme maps to AutoInit in GitHub API
if addReadme, ok := actions.GetBoolArg(args, "add_readme"); ok {
newRepo.AutoInit = github.Ptr(addReadme)
}
if gitignoreTemplate, ok := actions.GetStringArg(args, "gitignore_template"); ok && gitignoreTemplate != "" {
newRepo.GitignoreTemplate = github.Ptr(gitignoreTemplate)
}
if licenseTemplate, ok := actions.GetStringArg(args, "license_template"); ok && licenseTemplate != "" {
newRepo.LicenseTemplate = github.Ptr(licenseTemplate)
}
// add_readme maps to AutoInit in GitHub API
autoInit := true
if addReadme, ok := actions.GetBoolArg(args, "add_readme"); ok {
autoInit = addReadme
}
newRepo.AutoInit = github.Ptr(autoInit)
if gitignoreTemplate, ok := actions.GetStringArg(args, "gitignore_template"); ok && gitignoreTemplate != "" {
newRepo.GitignoreTemplate = github.Ptr(gitignoreTemplate)
}
if licenseTemplate, ok := actions.GetStringArg(args, "license_template"); ok && licenseTemplate != "" {
newRepo.LicenseTemplate = github.Ptr(licenseTemplate)
}
if (newRepo.GitignoreTemplate != nil || newRepo.LicenseTemplate != nil) && !autoInit {
return nil, nil, fmt.Errorf("gitignore_template/license_template require add_readme=true")
}
🤖 Prompt for AI Agents
In `@pkg/connector/repository.go` around lines 609 - 620, The code currently sets
newRepo.GitignoreTemplate and newRepo.LicenseTemplate without ensuring
newRepo.AutoInit is true; update the logic in the repository creation block
(symbols: newRepo, AutoInit, GitignoreTemplate, LicenseTemplate,
actions.GetBoolArg, actions.GetStringArg) to enforce GitHub's requirement: if
either gitignore_template or license_template is provided, ensure
newRepo.AutoInit is true—if the user explicitly passed add_readme=false return
an error indicating templates require auto_init=true, otherwise set
newRepo.AutoInit = github.Ptr(true) before assigning the templates.

Comment on lines +450 to +472
Name: "maintainers",
DisplayName: "Maintainers",
Description: "List GitHub usernames for organization members who will become team maintainers.",
Field: &config.Field_ResourceIdSliceField{
ResourceIdSliceField: &config.ResourceIdSliceField{
Rules: &config.RepeatedResourceIdRules{
AllowedResourceTypeIds: []string{resourceTypeUser.Id},
},
},
},
},
{
Name: "repo_names",
DisplayName: "Repository names",
Description: "The full name (e.g., organization-name/repository-name) of repositories to add the team to.",
Field: &config.Field_ResourceIdSliceField{
ResourceIdSliceField: &config.ResourceIdSliceField{
Rules: &config.RepeatedResourceIdRules{
AllowedResourceTypeIds: []string{resourceTypeRepository.Id},
},
},
},
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Argument descriptions don’t match the ResourceId inputs.

maintainers and repo_names are ResourceIdSliceFields and are parsed as numeric IDs, but the descriptions imply usernames/full names. This can mislead API/CLI callers. Consider updating the descriptions to reflect resource selection/IDs.

✏️ Suggested copy tweak
 			{
 				Name:        "maintainers",
 				DisplayName: "Maintainers",
-				Description: "List GitHub usernames for organization members who will become team maintainers.",
+				Description: "Select organization users (resource IDs) who will become team maintainers.",
 				Field: &config.Field_ResourceIdSliceField{
 					ResourceIdSliceField: &config.ResourceIdSliceField{
 						Rules: &config.RepeatedResourceIdRules{
 							AllowedResourceTypeIds: []string{resourceTypeUser.Id},
 						},
 					},
 				},
 			},
 			{
 				Name:        "repo_names",
 				DisplayName: "Repository names",
-				Description: "The full name (e.g., organization-name/repository-name) of repositories to add the team to.",
+				Description: "Select repositories (resource IDs) to add the team to.",
 				Field: &config.Field_ResourceIdSliceField{
 					ResourceIdSliceField: &config.ResourceIdSliceField{
 						Rules: &config.RepeatedResourceIdRules{
 							AllowedResourceTypeIds: []string{resourceTypeRepository.Id},
 						},
 					},
 				},
 			},
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Name: "maintainers",
DisplayName: "Maintainers",
Description: "List GitHub usernames for organization members who will become team maintainers.",
Field: &config.Field_ResourceIdSliceField{
ResourceIdSliceField: &config.ResourceIdSliceField{
Rules: &config.RepeatedResourceIdRules{
AllowedResourceTypeIds: []string{resourceTypeUser.Id},
},
},
},
},
{
Name: "repo_names",
DisplayName: "Repository names",
Description: "The full name (e.g., organization-name/repository-name) of repositories to add the team to.",
Field: &config.Field_ResourceIdSliceField{
ResourceIdSliceField: &config.ResourceIdSliceField{
Rules: &config.RepeatedResourceIdRules{
AllowedResourceTypeIds: []string{resourceTypeRepository.Id},
},
},
},
},
{
Name: "maintainers",
DisplayName: "Maintainers",
Description: "Select organization users (resource IDs) who will become team maintainers.",
Field: &config.Field_ResourceIdSliceField{
ResourceIdSliceField: &config.ResourceIdSliceField{
Rules: &config.RepeatedResourceIdRules{
AllowedResourceTypeIds: []string{resourceTypeUser.Id},
},
},
},
},
{
Name: "repo_names",
DisplayName: "Repository names",
Description: "Select repositories (resource IDs) to add the team to.",
Field: &config.Field_ResourceIdSliceField{
ResourceIdSliceField: &config.ResourceIdSliceField{
Rules: &config.RepeatedResourceIdRules{
AllowedResourceTypeIds: []string{resourceTypeRepository.Id},
},
},
},
},
🤖 Prompt for AI Agents
In `@pkg/connector/team.go` around lines 450 - 472, The argument descriptions for
the fields named "maintainers" and "repo_names" are misleading because both are
ResourceIdSliceField (parsed as numeric resource IDs) but the text implies
usernames/full names; update the DisplayName/Description strings for the fields
defined in this block (the "maintainers" and "repo_names" Field entries using
config.Field_ResourceIdSliceField / config.ResourceIdSliceField and
AllowedResourceTypeIds referencing resourceTypeUser.Id and
resourceTypeRepository.Id) to clearly state they expect resource IDs (e.g.,
"List of user resource IDs" and "List of repository resource IDs") so callers
understand these are ID selections not textual usernames/full names.

Comment on lines +536 to +548
if privacy, ok := actions.GetStringArg(args, "privacy"); ok && privacy != "" {
if isNestedTeam {
// Nested teams can only be "closed"
if privacy == teamPrivacySecret {
l.Warn("github-connector: secret privacy not allowed for nested teams, using closed",
zap.String("requested_privacy", privacy),
)
}
newTeam.Privacy = github.Ptr(teamPrivacyClosed)
} else if privacy == teamPrivacySecret || privacy == teamPrivacyClosed {
// Non-nested teams can be "secret" or "closed"
newTeam.Privacy = github.Ptr(privacy)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Search for the team.go file more broadly
find . -name "team.go" -type f 2>/dev/null | head -10

Repository: ConductorOne/baton-github

Length of output: 92


🏁 Script executed:

# Broader search in pkg directory
git ls-files pkg/connector/ | grep -i team

Repository: ConductorOne/baton-github

Length of output: 117


🏁 Script executed:

# Check repository structure
git ls-files | grep -E "connector|team" | head -30

Repository: ConductorOne/baton-github

Length of output: 1760


🏁 Script executed:

# Search for privacy-related code patterns
rg -t go "teamPrivacy" --max-count 20

Repository: ConductorOne/baton-github

Length of output: 473


🏁 Script executed:

# Search for teamPrivacy constants
rg "teamPrivacy(Secret|Closed)" -t go

Repository: ConductorOne/baton-github

Length of output: 473


🏁 Script executed:

# Search for the function containing this code
rg "if isNestedTeam" -B 20 -A 30 -t go

Repository: ConductorOne/baton-github

Length of output: 3972


🏁 Script executed:

# Also look for the pattern mentioned in the code
rg "Nested teams can only be" -B 10 -A 10

Repository: ConductorOne/baton-github

Length of output: 1345


🏁 Script executed:

# Search for all Go files with the pattern
rg "privacy == teamPrivacy" -t go -A 2 -B 2

Repository: ConductorOne/baton-github

Length of output: 748


🏁 Script executed:

# Broader search for github.Ptr
rg "github\.Ptr.*privacy" -t go

Repository: ConductorOne/baton-github

Length of output: 131


🏁 Script executed:

# Search for teamPrivacy variable/constant definitions
rg "var.*teamPrivacy|const.*teamPrivacy" -t go

Repository: ConductorOne/baton-github

Length of output: 51


🏁 Script executed:

# Look for error return patterns to understand how errors are returned in this file
rg "return.*error|return nil.*error" pkg/connector/team.go | head -10

Repository: ConductorOne/baton-github

Length of output: 212


🏁 Script executed:

# Get the exact function containing this code
rg -B 50 "if privacy, ok := actions.GetStringArg" pkg/connector/team.go | head -60

Repository: ConductorOne/baton-github

Length of output: 1769


🏁 Script executed:

# Check around line 500-560 to get context
sed -n '480,560p' pkg/connector/team.go

Repository: ConductorOne/baton-github

Length of output: 2876


🏁 Script executed:

# Also check if there are any tests that show expected behavior
grep -n "privacy" pkg/connector/team_test.go | head -20

Repository: ConductorOne/baton-github

Length of output: 51


🏁 Script executed:

# Get full context with line numbers
awk 'NR>=500 && NR<=570' pkg/connector/team.go | cat -n

Repository: ConductorOne/baton-github

Length of output: 3176


🏁 Script executed:

# Also get earlier context to find function signature
sed -n '440,540p' pkg/connector/team.go

Repository: ConductorOne/baton-github

Length of output: 3591


🏁 Script executed:

# Also check if there are any integration tests or examples
grep -rn "handleCreateTeamAction" pkg/connector/ | head -10

Repository: ConductorOne/baton-github

Length of output: 297


🏁 Script executed:

# Get validation patterns from the same function to understand error handling style
sed -n '487,700p' pkg/connector/team.go | grep -n "fmt.Errorf\|return"

Repository: ConductorOne/baton-github

Length of output: 1184


Add validation to reject invalid privacy values for non-nested teams.

The current code silently ignores invalid privacy values for non-nested teams, allowing the GitHub API to apply a default instead. This is inconsistent with the error validation pattern used throughout the function (e.g., invalid parent team ID, invalid maintainer ID) and with the nested-team behavior, which warns the user.

Add an upfront validation check that returns an error for invalid values:

🔧 Suggested fix
 	if privacy, ok := actions.GetStringArg(args, "privacy"); ok && privacy != "" {
+		if privacy != teamPrivacySecret && privacy != teamPrivacyClosed {
+			return nil, nil, fmt.Errorf("invalid privacy: %q (must be \"secret\" or \"closed\")", privacy)
+		}
 		if isNestedTeam {
 			// Nested teams can only be "closed"
 			if privacy == teamPrivacySecret {
 				l.Warn("github-connector: secret privacy not allowed for nested teams, using closed",
 					zap.String("requested_privacy", privacy),
 				)
 			}
 			newTeam.Privacy = github.Ptr(teamPrivacyClosed)
-		} else if privacy == teamPrivacySecret || privacy == teamPrivacyClosed {
+		} else {
 			// Non-nested teams can be "secret" or "closed"
 			newTeam.Privacy = github.Ptr(privacy)
 		}
🤖 Prompt for AI Agents
In `@pkg/connector/team.go` around lines 536 - 548, The privacy branch currently
only sets newTeam.Privacy for allowed values and silently ignores invalid values
for non-nested teams; add an explicit validation for the "privacy" arg when
isNestedTeam is false: if privacy is provided and is not teamPrivacySecret or
teamPrivacyClosed, return an input/argument error (consistent with how invalid
parent/maintainer IDs are handled) instead of letting the GitHub API default it.
Locate the block using actions.GetStringArg(args, "privacy"), isNestedTeam,
teamPrivacySecret, teamPrivacyClosed and newTeam.Privacy and insert the
validation+early return for invalid non-nested privacy values.

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.

4 participants