Skip to content

Commit a1e6744

Browse files
authored
fix(github): fix response bugs and add missing endpoint coverage (#5471)
* fix(github): fix response bugs and add missing endpoint coverage - Fix unauthenticated internal sub-fetch in pr.ts (list_pr_files) that 401'd on private repos and burned anon rate limits - Fix hardcoded/placeholder output fields in add_labels, delete_comment, delete_file - Handle 409 (sha mismatch) in merge_pr in addition to 405 - Loosen request_reviewers.reviewers to optional (team-only reviews) - Add items schema to get_commit parents array output - Add github_get_readme, github_create_pr_review, github_get_latest_release, github_list_tags (+ v2 variants) * fix(github): address review findings on pr.ts and delete_comment.ts - pr.ts: surface a real failure instead of silently returning success:true with an empty files array when the files sub-fetch fails; unify the sub-fetch Accept header with the rest of the file - delete_comment: success now tracks the actual deletion outcome instead of being hardcoded true * fix(github): guard new tools against non-2xx GitHub responses list_tags, get_readme, get_latest_release, and create_pr_review (v1 + v2) now check response.ok before parsing the payload as success data, returning success:false with a real error message instead of crashing on .map() or silently returning undefined fields when GitHub returns a 404/422/etc.
1 parent 9e3fc1f commit a1e6744

15 files changed

Lines changed: 1067 additions & 35 deletions

apps/sim/blocks/blocks/github.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,15 @@ export const GitHubBlock: BlockConfig<GitHubResponse> = {
4444
{ label: 'Get PR files', id: 'github_get_pr_files' },
4545
{ label: 'Close pull request', id: 'github_close_pr' },
4646
{ label: 'Request PR reviewers', id: 'github_request_reviewers' },
47+
{ label: 'Create PR review', id: 'github_create_pr_review' },
4748
// File Operations
4849
{ label: 'Get file content', id: 'github_get_file_content' },
4950
{ label: 'Create file', id: 'github_create_file' },
5051
{ label: 'Update file', id: 'github_update_file' },
5152
{ label: 'Delete file', id: 'github_delete_file' },
5253
{ label: 'Get directory tree', id: 'github_get_tree' },
54+
{ label: 'Get README', id: 'github_get_readme' },
55+
{ label: 'List tags', id: 'github_list_tags' },
5356
// Branch Operations
5457
{ label: 'List branches', id: 'github_list_branches' },
5558
{ label: 'Get branch', id: 'github_get_branch' },
@@ -71,6 +74,7 @@ export const GitHubBlock: BlockConfig<GitHubResponse> = {
7174
{ label: 'Update release', id: 'github_update_release' },
7275
{ label: 'List releases', id: 'github_list_releases' },
7376
{ label: 'Get release', id: 'github_get_release' },
77+
{ label: 'Get latest release', id: 'github_get_latest_release' },
7478
{ label: 'Delete release', id: 'github_delete_release' },
7579
// Workflow Operations
7680
{ label: 'List workflows', id: 'github_list_workflows' },
@@ -1598,6 +1602,68 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
15981602
},
15991603
mode: 'advanced',
16001604
},
1605+
// Create PR review parameters
1606+
{
1607+
id: 'pullNumber',
1608+
title: 'Pull Request Number',
1609+
type: 'short-input',
1610+
placeholder: 'e.g., 123',
1611+
required: true,
1612+
condition: { field: 'operation', value: 'github_create_pr_review' },
1613+
},
1614+
{
1615+
id: 'event',
1616+
title: 'Review Action',
1617+
type: 'dropdown',
1618+
options: [
1619+
{ label: 'Approve', id: 'APPROVE' },
1620+
{ label: 'Request changes', id: 'REQUEST_CHANGES' },
1621+
{ label: 'Comment', id: 'COMMENT' },
1622+
],
1623+
required: true,
1624+
condition: { field: 'operation', value: 'github_create_pr_review' },
1625+
},
1626+
{
1627+
id: 'body',
1628+
title: 'Review Body',
1629+
type: 'long-input',
1630+
placeholder: 'Review text (required for Request changes and Comment)',
1631+
condition: { field: 'operation', value: 'github_create_pr_review' },
1632+
},
1633+
{
1634+
id: 'commit_id',
1635+
title: 'Commit SHA',
1636+
type: 'short-input',
1637+
placeholder: 'e.g., 6dcb09b (defaults to the latest commit)',
1638+
condition: { field: 'operation', value: 'github_create_pr_review' },
1639+
mode: 'advanced',
1640+
},
1641+
// Get README parameters
1642+
{
1643+
id: 'ref',
1644+
title: 'Git Reference',
1645+
type: 'short-input',
1646+
placeholder: 'e.g., main (leave empty for default branch)',
1647+
condition: { field: 'operation', value: 'github_get_readme' },
1648+
mode: 'advanced',
1649+
},
1650+
// List tags parameters
1651+
{
1652+
id: 'per_page',
1653+
title: 'Results Per Page',
1654+
type: 'short-input',
1655+
placeholder: 'e.g., 30 (default: 30, max: 100)',
1656+
condition: { field: 'operation', value: 'github_list_tags' },
1657+
mode: 'advanced',
1658+
},
1659+
{
1660+
id: 'page',
1661+
title: 'Page Number',
1662+
type: 'short-input',
1663+
placeholder: 'e.g., 1',
1664+
condition: { field: 'operation', value: 'github_list_tags' },
1665+
mode: 'advanced',
1666+
},
16011667
],
16021668
tools: {
16031669
access: [
@@ -1619,12 +1685,15 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
16191685
'github_get_pr_files',
16201686
'github_close_pr',
16211687
'github_request_reviewers',
1688+
'github_create_pr_review',
16221689
// File tools
16231690
'github_get_file_content',
16241691
'github_create_file',
16251692
'github_update_file',
16261693
'github_delete_file',
16271694
'github_get_tree',
1695+
'github_get_readme',
1696+
'github_list_tags',
16281697
// Branch tools
16291698
'github_list_branches',
16301699
'github_get_branch',
@@ -1646,6 +1715,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
16461715
'github_update_release',
16471716
'github_list_releases',
16481717
'github_get_release',
1718+
'github_get_latest_release',
16491719
'github_delete_release',
16501720
// Workflow tools
16511721
'github_list_workflows',
@@ -1737,6 +1807,8 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
17371807
return 'github_close_pr'
17381808
case 'github_request_reviewers':
17391809
return 'github_request_reviewers'
1810+
case 'github_create_pr_review':
1811+
return 'github_create_pr_review'
17401812
// File operations
17411813
case 'github_get_file_content':
17421814
return 'github_get_file_content'
@@ -1748,6 +1820,10 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
17481820
return 'github_delete_file'
17491821
case 'github_get_tree':
17501822
return 'github_get_tree'
1823+
case 'github_get_readme':
1824+
return 'github_get_readme'
1825+
case 'github_list_tags':
1826+
return 'github_list_tags'
17511827
// Branch operations
17521828
case 'github_list_branches':
17531829
return 'github_list_branches'
@@ -1787,6 +1863,8 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
17871863
return 'github_list_releases'
17881864
case 'github_get_release':
17891865
return 'github_get_release'
1866+
case 'github_get_latest_release':
1867+
return 'github_get_latest_release'
17901868
case 'github_delete_release':
17911869
return 'github_delete_release'
17921870
// Workflow operations
@@ -1917,6 +1995,8 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
19171995
commit_title: { type: 'string', description: 'Commit title' },
19181996
reviewers: { type: 'string', description: 'Reviewer usernames' },
19191997
team_reviewers: { type: 'string', description: 'Team reviewer slugs' },
1998+
event: { type: 'string', description: 'PR review action' },
1999+
commit_id: { type: 'string', description: 'Commit SHA' },
19202000
// File parameters
19212001
content: { type: 'string', description: 'File content' },
19222002
message: { type: 'string', description: 'Commit message' },

apps/sim/tools/github/add_labels.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export const addLabelsTool: ToolConfig<AddLabelsParams, LabelsResponse> = {
6060
},
6161
},
6262

63-
transformResponse: async (response) => {
63+
transformResponse: async (response, params) => {
6464
const labelsData = await response.json()
6565

6666
const labels = labelsData.map((label: any) => label.name)
@@ -74,8 +74,10 @@ All labels on issue: ${labels.join(', ')}`
7474
content,
7575
metadata: {
7676
labels,
77-
issue_number: 0, // Will be filled from params in actual implementation
78-
html_url: '', // Will be constructed from params
77+
issue_number: params?.issue_number ?? 0,
78+
html_url: params
79+
? `https://github.com/${params.owner}/${params.repo}/issues/${params.issue_number}`
80+
: '',
7981
},
8082
},
8183
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import type { CreatePRReviewParams, PRReviewResponse } from '@/tools/github/types'
2+
import { USER_OUTPUT } from '@/tools/github/types'
3+
import type { ToolConfig } from '@/tools/types'
4+
5+
export const createPRReviewTool: ToolConfig<CreatePRReviewParams, PRReviewResponse> = {
6+
id: 'github_create_pr_review',
7+
name: 'GitHub Create PR Review',
8+
description:
9+
'Submit a review for a pull request. Use APPROVE, REQUEST_CHANGES, or COMMENT. A body is required for REQUEST_CHANGES and COMMENT reviews.',
10+
version: '1.0.0',
11+
12+
params: {
13+
owner: {
14+
type: 'string',
15+
required: true,
16+
visibility: 'user-or-llm',
17+
description: 'Repository owner',
18+
},
19+
repo: {
20+
type: 'string',
21+
required: true,
22+
visibility: 'user-or-llm',
23+
description: 'Repository name',
24+
},
25+
pullNumber: {
26+
type: 'number',
27+
required: true,
28+
visibility: 'user-or-llm',
29+
description: 'Pull request number',
30+
},
31+
event: {
32+
type: 'string',
33+
required: true,
34+
visibility: 'user-or-llm',
35+
description: 'The review action to perform: APPROVE, REQUEST_CHANGES, or COMMENT',
36+
},
37+
body: {
38+
type: 'string',
39+
required: false,
40+
visibility: 'user-or-llm',
41+
description: 'The body text of the review (required for REQUEST_CHANGES and COMMENT)',
42+
},
43+
commit_id: {
44+
type: 'string',
45+
required: false,
46+
visibility: 'user-or-llm',
47+
description: 'The SHA of the commit that needs a review (defaults to the most recent commit)',
48+
},
49+
apiKey: {
50+
type: 'string',
51+
required: true,
52+
visibility: 'user-only',
53+
description: 'GitHub API token',
54+
},
55+
},
56+
57+
request: {
58+
url: (params) =>
59+
`https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}/reviews`,
60+
method: 'POST',
61+
headers: (params) => ({
62+
Accept: 'application/vnd.github+json',
63+
Authorization: `Bearer ${params.apiKey}`,
64+
'X-GitHub-Api-Version': '2022-11-28',
65+
}),
66+
body: (params) => {
67+
const body: Record<string, any> = {
68+
event: params.event,
69+
}
70+
if (params.body) body.body = params.body
71+
if (params.commit_id) body.commit_id = params.commit_id
72+
return body
73+
},
74+
},
75+
76+
transformResponse: async (response) => {
77+
if (!response.ok) {
78+
const error = await response.json().catch(() => ({}))
79+
return {
80+
success: false,
81+
error: error.message || `Failed to submit PR review (HTTP ${response.status})`,
82+
output: {
83+
content: '',
84+
metadata: { id: 0, state: '', body: '', html_url: '', commit_id: '' },
85+
},
86+
}
87+
}
88+
89+
const review = await response.json()
90+
91+
const content = `Review submitted for PR #${review.pull_request_url?.split('/').pop() ?? ''}
92+
State: ${review.state}
93+
URL: ${review.html_url}`
94+
95+
return {
96+
success: true,
97+
output: {
98+
content,
99+
metadata: {
100+
id: review.id,
101+
state: review.state,
102+
body: review.body ?? '',
103+
html_url: review.html_url,
104+
commit_id: review.commit_id,
105+
},
106+
},
107+
}
108+
},
109+
110+
outputs: {
111+
content: { type: 'string', description: 'Human-readable review confirmation' },
112+
metadata: {
113+
type: 'object',
114+
description: 'Review metadata',
115+
properties: {
116+
id: { type: 'number', description: 'Review ID' },
117+
state: {
118+
type: 'string',
119+
description: 'Review state (APPROVED/CHANGES_REQUESTED/COMMENTED)',
120+
},
121+
body: { type: 'string', description: 'Review body text' },
122+
html_url: { type: 'string', description: 'GitHub web URL for the review' },
123+
commit_id: { type: 'string', description: 'SHA of the reviewed commit' },
124+
},
125+
},
126+
},
127+
}
128+
129+
export const createPRReviewV2Tool: ToolConfig<CreatePRReviewParams, any> = {
130+
id: 'github_create_pr_review_v2',
131+
name: createPRReviewTool.name,
132+
description: createPRReviewTool.description,
133+
version: '2.0.0',
134+
params: createPRReviewTool.params,
135+
request: createPRReviewTool.request,
136+
137+
transformResponse: async (response: Response) => {
138+
if (!response.ok) {
139+
const error = await response.json().catch(() => ({}))
140+
return {
141+
success: false,
142+
error: error.message || `Failed to submit PR review (HTTP ${response.status})`,
143+
output: {
144+
id: 0,
145+
user: null,
146+
body: null,
147+
state: '',
148+
html_url: '',
149+
pull_request_url: '',
150+
commit_id: '',
151+
submitted_at: null,
152+
},
153+
}
154+
}
155+
156+
const review = await response.json()
157+
return {
158+
success: true,
159+
output: {
160+
id: review.id,
161+
user: review.user ?? null,
162+
body: review.body ?? null,
163+
state: review.state,
164+
html_url: review.html_url,
165+
pull_request_url: review.pull_request_url,
166+
commit_id: review.commit_id,
167+
submitted_at: review.submitted_at ?? null,
168+
},
169+
}
170+
},
171+
172+
outputs: {
173+
id: { type: 'number', description: 'Review ID' },
174+
user: { ...USER_OUTPUT, optional: true },
175+
body: { type: 'string', description: 'Review body text' },
176+
state: { type: 'string', description: 'Review state (APPROVED/CHANGES_REQUESTED/COMMENTED)' },
177+
html_url: { type: 'string', description: 'GitHub web URL for the review' },
178+
pull_request_url: { type: 'string', description: 'API URL of the reviewed pull request' },
179+
commit_id: { type: 'string', description: 'SHA of the reviewed commit' },
180+
submitted_at: { type: 'string', description: 'Review submission timestamp' },
181+
},
182+
}

apps/sim/tools/github/delete_comment.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,20 @@ export const deleteCommentTool: ToolConfig<DeleteCommentParams, DeleteCommentRes
4545
}),
4646
},
4747

48-
transformResponse: async (response) => {
49-
const content = `Comment #${response.url.split('/').pop()} successfully deleted`
48+
transformResponse: async (response, params) => {
49+
const deleted = response.status === 204
50+
const commentId = params?.comment_id ?? Number(response.url.split('/').pop())
51+
const content = deleted
52+
? `Comment #${commentId} successfully deleted`
53+
: `Failed to delete comment #${commentId}`
5054

5155
return {
52-
success: true,
56+
success: deleted,
5357
output: {
5458
content,
5559
metadata: {
56-
deleted: true,
57-
comment_id: Number(response.url.split('/').pop()),
60+
deleted,
61+
comment_id: commentId,
5862
},
5963
},
6064
}
@@ -82,10 +86,11 @@ export const deleteCommentV2Tool: ToolConfig<DeleteCommentParams, any> = {
8286
request: deleteCommentTool.request,
8387

8488
transformResponse: async (response: Response, params) => {
89+
const deleted = response.status === 204
8590
return {
86-
success: true,
91+
success: deleted,
8792
output: {
88-
deleted: response.status === 204,
93+
deleted,
8994
comment_id: params?.comment_id || 0,
9095
},
9196
}

0 commit comments

Comments
 (0)