fix(github): wire block fields that never reached the API - #7272
fix(github): wire block fields that never reached the API#7272waleedlatif1 wants to merge 7 commits into
Conversation
Nine GitHub block fields render, accept input, and are then discarded. A
subBlock binds to a tool param only when its id matches the param name
(`resolveSubBlockForParam`), and the block declares no `tools.config.params`
mapper and no `canonicalParamId`, so every field whose id merely resembles the
param it feeds is inert.
Three of them make an operation impossible rather than degraded:
- Create Gist's Public/Secret dropdown feeds `gist_public`, never `public`, and
`create_gist` defaults `public ?? false` — so every gist is created secret no
matter what the user picks. This one silently contradicts an explicit choice
about visibility.
- Both reaction operations require `content`, but the field is
`reaction_content`, so every call 422s.
- Create Milestone requires `title`, but the field is `milestone_title`.
Adds an operation-keyed alias table plus a mapper. Keyed by operation because
targets collide — `fork_sort` and `milestone_sort` both feed `sort` — and stored
block state keeps values for fields the current operation does not render, so a
flat table would let a stale sibling win. Every assignment is guarded: the
handler merges `{ ...inputs, ...params(inputs) }`, so an unconditional write
would clobber a model-supplied value with `undefined` on the agent path.
The ids are aliased rather than renamed because a subBlock id is persisted
workflow state; renaming needs a `_removed_` migration.
Also in the same mechanism: dropdown option ids are strings, so `'false'`
arrives truthy and every boolean param inverts. Coerced for the seven boolean
tool params reachable from a dropdown.
Update Branch Protection could not be driven at all: `restrictions` is required
by the API and had no field, and the other three required params were collapsed
under advanced mode and unmarked required, so the default form showed only
owner/repo/branch. Adds the field, marks all four required, and declares the
object-valued ones `json` in `inputs` so the handler parses them instead of
sending JSON text.
check_star could never answer "not starred". GitHub encodes this endpoint's
answer in the status line with no body — 204 starred, 404 not — and the executor
rejects every non-2xx before `transformResponse` runs, so the negative half was
unreachable and surfaced as a tool failure. Adds an opt-in `nonErrorStatuses` to
ToolConfig; absent that declaration nothing changes for any other tool.
list_workflow_runs rendered a "Workflow ID or Filename" field for a tool with no
such param, silently widening the query to every run in the repository. Adds the
param and the workflow-scoped endpoint. The id is percent-encoded because
`list_workflows` reports a workflow by `path` (`.github/workflows/ci.yml`) and
GitHub resolves that spelling only as `%2F` — a real slash 404s.
Every fix is pinned by a test verified red first, including a generic guard that
asserts every required tool param is reachable for every operation — the check
that would have caught all of these.
|
@greptile review |
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
@cubic review |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
Greptile SummaryThe PR repairs GitHub block-to-tool parameter wiring and several related request/response contracts.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| apps/sim/blocks/blocks/github.ts | Adds operation-scoped parameter aliases, boolean normalization, branch-protection fields, and V2 mapper inheritance. |
| apps/sim/tools/github/update_branch_protection.ts | Models nullable replacement settings and always emits GitHub’s four required protection keys. |
| apps/sim/tools/github/check_star.ts | Treats GitHub’s documented 404 response as the negative star-check result. |
| apps/sim/tools/github/list_workflow_runs.ts | Adds optional workflow scoping with encoded workflow identifiers and dot-segment rejection. |
| apps/sim/tools/index.ts | Allows explicitly declared non-error HTTP statuses to reach a tool’s response transformer. |
| apps/sim/tools/types.ts | Adds the opt-in nonErrorStatuses contract to HTTP tool configuration. |
Reviews (9): Last reviewed commit: "fix(github): let ListWorkflowRunsParams ..." | Re-trigger Greptile
There was a problem hiding this comment.
3 issues found across 12 files
Confidence score: 2/5
- In
apps/sim/blocks/blocks/github.ts, explicitnullbranch-protection settings are rejected as missing, so documented disable requests never reach GitHub; update required-parameter validation to allownullfor these JSON settings. - In
apps/sim/tools/github/list_workflow_runs.ts,workflow_idvalues of.or..can be normalized into a request for all repository runs, creating an unintended data-scope expansion; reject dot-segment values, including surrounding whitespace. - In
apps/sim/blocks/blocks/github.ts, selectingAllforgithub_list_branchessendsprotected=allinstead of a valid boolean, so the GitHub filter request is invalid; mapAlltoundefinedand omit the query parameter.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/sim/tools/github/list_workflow_runs.ts">
<violation number="1" location="apps/sim/tools/github/list_workflow_runs.ts:95">
P2: When `workflow_id` is `.` or `..` (including surrounding whitespace), `encodeURIComponent` leaves the dot segment unchanged and `new URL` normalizes it, so `..` silently requests all repository runs. Reject dot-segment identifiers before building the scoped path.</violation>
</file>
<file name="apps/sim/blocks/blocks/github.ts">
<violation number="1" location="apps/sim/blocks/blocks/github.ts:979">
P1: When a branch-protection setting is entered as `null`, required-parameter validation treats it as missing, so the documented disable request never reaches GitHub. Allow explicit null for these required JSON settings while continuing to reject omitted values.</violation>
<violation number="2" location="apps/sim/blocks/blocks/github.ts:2347">
P2: When users select `All` for `github_list_branches`, the mapper leaves `protected` as the string `all`, so the request sends an invalid boolean filter. Override this value to `undefined` to omit the query parameter, while retaining the `true`/`false` coercion.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
Marking the four branch-protection settings `required: true` made the documented way to switch one off unreachable. GitHub lists all four in the endpoint's `required` array and marks every one `nullable`: the key must be present in the body, and `null` is how a caller disables that protection. Sim's `required` means something stricter — present *and* not null — so `validateRequiredParametersAfterMerge` rejects `null` as a missing parameter before the request is ever built. The placeholders told users to type exactly the value that could not get through. Models the real contract instead: the params are optional, and the body always emits all four keys, sending `null` for anything unset. An omitted value cannot simply be dropped either — `JSON.stringify` strips `undefined`, and GitHub rejects the body for the missing key. The subBlocks stay visible and out of advanced mode, which was the point of surfacing them; they are just no longer required, and the placeholders now say to leave the field blank rather than to type null.
|
@greptile review |
|
@cubic review |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
3 issues found and verified against the latest diff
Confidence score: 3/5
- In
apps/sim/blocks/blocks/github.ts, the alias mapper can retain stale canonical values when a subBlock is blank after switching operations, causing unrelated milestone titles or descriptions to be sent; clear the mapped target when the alias is blank. - In
apps/sim/blocks/blocks/github.ts, List Branches with All currently sends the invalidprotected=allquery, which can produce incorrect requests; map All toundefinedso the protection filter is omitted. - In
apps/sim/blocks/blocks/github.test.ts, the mapper test makes no assertions and provides no subBlock values, so it cannot detect either parameter-mapping regression; add assertions covering blank aliases and the All branch option.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/sim/blocks/blocks/github.ts">
<violation number="1" location="apps/sim/blocks/blocks/github.ts:2341">
P2: When an alias subBlock is blank after switching operations, this `continue` leaves a stale canonical value in `finalInputs`, so unrelated milestone titles or descriptions can be sent. Clear the mapped target when the alias key is present, while still skipping absent alias keys to preserve direct model-supplied params.</violation>
<violation number="2" location="apps/sim/blocks/blocks/github.ts:2348">
P2: When users select All for List Branches, the mapper leaves `'all'` as a string and the tool sends the invalid `protected=all` query. Map this option to `undefined` so the request omits the protection filter.</violation>
</file>
<file name="apps/sim/blocks/blocks/github.test.ts">
<violation number="1" location="apps/sim/blocks/blocks/github.test.ts:127">
P3: This test never asserts anything: it calls `mapParams({ operation })` with no subBlock values, and the mapper (github.ts lines 2340-2350) skips every alias whose value is `undefined`/`null`/`''` and only writes a boolean when the value is exactly `'true'`/`'false'`. So `mapped` is `{}` for every operation and the inner `for...of` iterates zero entries, meaning the `expect(...).not.toBeUndefined()` guard can never fail regardless of implementation. Either feed it realistic values so it actually exercises a non-empty map, or remove it, since the 'supplies every required tool param' test already covers undefined emissions.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
The scoped workflow-runs path I added reintroduced, in one parameter, the exact
over-broad query this PR set out to fix.
`encodeURIComponent('..')` returns `'..'` verbatim — a dot segment is made
entirely of unreserved characters — and the WHATWG parser that `fetch` uses then
removes it and pops a path segment. So `workflow_id: '..'` resolved back to
`/repos/{owner}/{repo}/actions/runs` and silently listed every run in the
repository, with nothing to say the scope had been dropped. `'.'` produced a
bogus path the same way.
Only rejection closes this; no encoding scheme neutralizes a dot segment. A dot
segment *inside* a longer value is already inert and stays accepted, because its
separators survive as `%2F` and the parser does not decode those before removing
dot segments — `.github/workflows/../ci.yml` is preserved intact.
This matches `safeEncodedUrlPathSegment` in #7262 exactly, so the rebase is a
clean swap for that helper.
|
@greptile review |
|
@cubic review |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
2 issues found and verified against the latest diff
Confidence score: 2/5
apps/sim/tools/github/update_branch_protection.tscan clear every branch-protection setting omitted by the caller, unintentionally disabling protections during partial updates. Preserve existing settings before constructing the PUT payload.apps/sim/blocks/blocks/github.tssends blank JSON fields as"", causing GitHub to reject updates instead of disabling those settings. Normalize blank strings tonullbefore building the tool request.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/sim/blocks/blocks/github.ts">
<violation number="1" location="apps/sim/blocks/blocks/github.ts:978">
P1: When a user leaves a branch-protection JSON field blank, the request sends `""` instead of `null`, so GitHub rejects the update rather than disabling that setting. Normalize blank strings to `null` before the tool request (or make the request builder treat blank strings as null).</violation>
</file>
<file name="apps/sim/tools/github/update_branch_protection.ts">
<violation number="1" location="apps/sim/tools/github/update_branch_protection.ts:88">
P1: When a caller supplies only some branch-protection settings, this PUT sends `null` for every omitted setting and disables protections the caller did not intend to change. Preserve the existing settings before building the full replacement body, or require callers to provide the complete configuration.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
| * `undefined`, and GitHub rejects the body for the missing key. | ||
| */ | ||
| const body: Record<string, unknown> = { | ||
| required_status_checks: params.required_status_checks ?? null, |
There was a problem hiding this comment.
P1: When a caller supplies only some branch-protection settings, this PUT sends null for every omitted setting and disables protections the caller did not intend to change. Preserve the existing settings before building the full replacement body, or require callers to provide the complete configuration.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/sim/tools/github/update_branch_protection.ts, line 88:
<comment>When a caller supplies only some branch-protection settings, this PUT sends `null` for every omitted setting and disables protections the caller did not intend to change. Preserve the existing settings before building the full replacement body, or require callers to provide the complete configuration.</comment>
<file context>
@@ -77,11 +77,18 @@ export const updateBranchProtectionTool: ToolConfig<
+ * `undefined`, and GitHub rejects the body for the missing key.
+ */
+ const body: Record<string, unknown> = {
+ required_status_checks: params.required_status_checks ?? null,
+ enforce_admins: params.enforce_admins ?? null,
+ required_pull_request_reviews: params.required_pull_request_reviews ?? null,
</file context>
There was a problem hiding this comment.
Real, and worth stating in the tool — fixed in bc5e8df, though not the way suggested.
You're right that PUT .../protection sends all four keys and that an omitted setting is therefore disabled, not preserved. That is GitHub's design: the endpoint sets the whole configuration at once, every key is in its required list, and there is no merge form of it.
I didn't add a read-modify-write. Fetching current protection and merging would race a concurrent change (two agents each PUT a merge of a config that has since moved, and one silently reverts the other), and it would change a documented replace endpoint into something that behaves differently from every other client, including gh. Quietly preserving settings the caller didn't mention is its own surprise.
What was actually missing is that nothing said so — especially on the model path, where the description is the whole contract. The tool description now leads with it, and each of the four params says "Leave out to DISABLE … this endpoint replaces the whole configuration, it does not merge."
Worth noting this operation could not be invoked at all before this PR — restrictions was required by the API and had no field, so every call 422'd. So there's no behaviour being regressed here, only a footgun that is now documented where a caller reads it.
There was a problem hiding this comment.
3 issues found across 14 files
Confidence score: 2/5
apps/sim/tools/github/update_branch_protection.tscan clear every existing branch-protection rule when a caller updates only one field, potentially weakening repository security; require explicit replacement values or fetch and merge the current protection settings.apps/sim/blocks/blocks/github.tsleaves cleared protection fields as empty strings, causing GitHub to reject requests instead of disabling those rules; normalize empty values tonullbefore sending the update.apps/sim/blocks/blocks/github.test.tsdoes not exercise its assertion becausemapParamsreturns an empty object, leaving the protection-parameter behavior unverified; provide a non-empty mapped input and assert the resulting values.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/sim/tools/github/update_branch_protection.ts">
<violation number="1" location="apps/sim/tools/github/update_branch_protection.ts:88">
P1: When a caller updates only one protection rule, this `PUT` sends `null` for every omitted rule and disables the existing protections. Require an explicit value for each replacement field, or fetch and merge the current protection settings before issuing the update.</violation>
</file>
<file name="apps/sim/blocks/blocks/github.ts">
<violation number="1" location="apps/sim/blocks/blocks/github.ts:978">
P2: When a branch-protection JSON field is cleared, the handler leaves `''` unchanged and the API request sends it instead of `null`, so GitHub rejects the update rather than disabling that rule. Normalize empty values to `null` before building the request body.</violation>
</file>
<file name="apps/sim/blocks/blocks/github.test.ts">
<violation number="1" location="apps/sim/blocks/blocks/github.test.ts:127">
P3: This test is a no-op: `mapParams({ operation })` always returns `{}` because the mapper skips undefined/null/empty alias values and only coerces the exact strings 'true'/'false', so the inner `expect` never executes for any operation. It passes vacuously and provides false confidence that aliases never emit undefined. Remove it, or drive it with a source value for each alias so it actually asserts something.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
| * `undefined`, and GitHub rejects the body for the missing key. | ||
| */ | ||
| const body: Record<string, unknown> = { | ||
| required_status_checks: params.required_status_checks ?? null, |
There was a problem hiding this comment.
P1: When a caller updates only one protection rule, this PUT sends null for every omitted rule and disables the existing protections. Require an explicit value for each replacement field, or fetch and merge the current protection settings before issuing the update.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/sim/tools/github/update_branch_protection.ts, line 88:
<comment>When a caller updates only one protection rule, this `PUT` sends `null` for every omitted rule and disables the existing protections. Require an explicit value for each replacement field, or fetch and merge the current protection settings before issuing the update.</comment>
<file context>
@@ -77,11 +77,18 @@ export const updateBranchProtectionTool: ToolConfig<
+ * `undefined`, and GitHub rejects the body for the missing key.
+ */
+ const body: Record<string, unknown> = {
+ required_status_checks: params.required_status_checks ?? null,
+ enforce_admins: params.enforce_admins ?? null,
+ required_pull_request_reviews: params.required_pull_request_reviews ?? null,
</file context>
There was a problem hiding this comment.
Same point Greptile raised on the adjacent line — answered in full there, and addressed in bc5e8df by documentation rather than code.
Short version: PUT .../protection is a replace endpoint by GitHub's design (all four settings are in the schema's required array and all four are nullable), so "omitted means disabled" is the contract itself. Requiring a non-null value for each is exactly the bug the previous commit fixed — it makes disabling unreachable. Fetching and merging races a concurrent change and diverges from every other client of the endpoint.
The tool and per-param descriptions now say plainly that the call replaces the whole configuration and that an omitted setting is disabled. See the neighbouring thread for the full reasoning and for the enforcement shape I'd pick if you want it in code instead.
Blank fields, a tri-state filter, and stale block state each defeated the mapper in a different way. **A blank field disabled nothing.** A cleared `short-input` arrives as `''`, and the handler's JSON coercion only parses non-blank strings, so the blank reached the branch-protection body verbatim and GitHub rejected `""` where it expects an object or null. Blank now means null, which is what the placeholder promises. **A stale sibling could be sent.** The guard that stopped the mapper clobbering a model-supplied param also stopped it clearing one. Block state keeps values for fields the current operation does not render, so a leftover `title` from Create Issue could reach Update Milestone when its own field was blank. The rule is now about presence, not truthiness: an absent alias key means the caller addressed the tool param directly and is left alone; a key present but blank means the operator cleared the field and the target is cleared with it. **"All" was sent as a filter.** `protected` is a tri-state in the UI and a boolean on the wire, and `list_branches` appends the filter whenever it is not undefined — so the sentinel went out as the invalid `protected=all`. It now becomes an omission. **Replace semantics were not stated.** `PUT .../protection` sets the whole configuration at once, so omitting a setting disables it. That is GitHub's design and not something the tool can merge away without a read-modify-write that would race a concurrent change, but a caller — especially a model — has to be told. The tool and per-param descriptions now say it plainly. Also replaces a test that could not fail: it asserted over a map that was always empty, so it never exercised the mapper. The presence rule it was meant to protect is now pinned by two tests that distinguish an absent alias key from a blank one.
|
@greptile review |
|
@cubic review |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
|
@greptile review |
There was a problem hiding this comment.
2 issues found across 15 files
Confidence score: 3/5
- In
apps/sim/tools/github/list_workflow_runs.ts, an invalid presentworkflow_idsuch astruecan broaden the request from one workflow to all runs, creating a concrete correctness and potentially costly data-scope risk; reject invalid values instead of treating them as omitted. - In
apps/sim/tools/github/list_workflow_runs.ts, trimming a valid workflow filename can alter its identifier and cause the wrong workflow lookup or a 404; preserve the raw filename while using trimming only to detect omission.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/sim/tools/github/list_workflow_runs.ts">
<violation number="1" location="apps/sim/tools/github/list_workflow_runs.ts:91">
P2: When `workflow_id` is present with an invalid runtime type such as `true`, this fallback silently widens the request to every workflow run. Reject invalid present values instead of treating them as an omitted workflow.</violation>
<violation number="2" location="apps/sim/tools/github/list_workflow_runs.ts:93">
P2: When a valid workflow filename contains significant surrounding whitespace, `.trim()` changes the identifier and can query the wrong workflow or return 404. Preserve the raw filename, using trimming only to detect omission if that behavior is required.
(Based on your team's feedback about preserving whitespace in path identifiers.)</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
| */ | ||
| const workflowId = | ||
| typeof params.workflow_id === 'string' || typeof params.workflow_id === 'number' | ||
| ? String(params.workflow_id).trim() |
There was a problem hiding this comment.
P2: When a valid workflow filename contains significant surrounding whitespace, .trim() changes the identifier and can query the wrong workflow or return 404. Preserve the raw filename, using trimming only to detect omission if that behavior is required.
(Based on your team's feedback about preserving whitespace in path identifiers.)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/sim/tools/github/list_workflow_runs.ts, line 93:
<comment>When a valid workflow filename contains significant surrounding whitespace, `.trim()` changes the identifier and can query the wrong workflow or return 404. Preserve the raw filename, using trimming only to detect omission if that behavior is required.
(Based on your team's feedback about preserving whitespace in path identifiers.) </comment>
<file context>
@@ -76,9 +83,31 @@ export const listWorkflowRunsTool: ToolConfig<ListWorkflowRunsParams, ListWorkfl
+ */
+ const workflowId =
+ typeof params.workflow_id === 'string' || typeof params.workflow_id === 'number'
+ ? String(params.workflow_id).trim()
+ : ''
+ /**
</file context>
There was a problem hiding this comment.
Deliberate here, and I want to explain rather than just decline — the learning you're applying is real, but it belongs to the other half of a split this repo makes on purpose.
#7262 introduces three path helpers precisely because these are two different kinds of value:
safeUrlPath— a real multi-segment file path, and it trims nowhere. A leading or trailing space is a legal git filename (docs/my file .txtis tracked verbatim), so trimming would silently address a different file. That is the feedback you're citing, and it is correct forpathonupdate_file/delete_file.safeUrlPathSegment/safeEncodedUrlPathSegment— one opaque, copy-pasted identifier, and both trim, because surrounding whitespace there is transport noise.
workflow_id is in the second group. #7262 guards this exact parameter with safeUrlPathSegment, which trims, and the encoded variant I mirror here trims too. Keeping .trim() is what makes this a clean swap for the shared helper on rebase; dropping it would put this one call site out of step with every other identifier in the integration.
The residual risk is a workflow file whose name has significant leading or trailing whitespace. GitHub Actions only reads workflows from .github/workflows/, and such a name would be pathological rather than merely unusual — against a copy-pasted id or filename picking up a stray space, which is routine. If the trimming rule for identifiers is ever revisited, it should change in url-path.ts for all of them at once rather than diverge here.
There was a problem hiding this comment.
Deliberate here — and the learning you're applying is real, it just belongs to the other half of a split this repo makes on purpose.
#7262 introduces three path helpers precisely because these are two different kinds of value:
safeUrlPath— a real multi-segment file path, which trims nowhere. A leading or trailing space is a legal git filename (docs/my file .txtis tracked verbatim), so trimming would silently address a different file. That is the feedback you're citing, and it is correct forpathonupdate_file/delete_file.safeUrlPathSegment/safeEncodedUrlPathSegment— one opaque, copy-pasted identifier. Both trim, because whitespace around an id is transport noise.
workflow_id is in the second group: #7262 guards this exact parameter with safeUrlPathSegment, and the encoded variant I mirror here trims too. Keeping .trim() is what makes this a clean swap for the shared helper on rebase; dropping it would put this one call site out of step with every other identifier in the integration.
The residual risk is a workflow file whose name carries significant leading or trailing whitespace. Actions only reads workflows from .github/workflows/, so such a name would be pathological rather than merely unusual — weighed against a copy-pasted id or filename picking up a stray space, which is routine. If the trimming rule for identifiers is revisited it should change in url-path.ts for all of them at once, not diverge here.
|
@cubic review |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
1 issue found across 15 files
Confidence score: 5/5
- In
apps/sim/blocks/blocks/github.test.ts,visibleSubBlocksmay omit sub-blocks when the operation condition is nested underand:, causing the test helper to disagree with what the editor renders; update the condition matching to handle nested clauses.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/sim/blocks/blocks/github.test.ts">
<violation number="1" location="apps/sim/blocks/blocks/github.test.ts:31">
P3: The `visibleSubBlocks` helper is documented as returning "The subBlocks the editor renders once operation is chosen", but it only evaluates the top-level `condition.field === 'operation'` match and drops the `and:` sub-condition. In `github.ts`, the `path` and `line` subBlocks (github.ts:1945-1980) are gated by `condition: { field: 'operation', value: 'github_comment', and: { field: 'commentType', value: 'file_comment' } }`, so the helper treats them as visible for the `github_comment` operation even though the editor only renders them when `commentType` is `file_comment`. The cast type also omits `and`, so TypeScript won't catch this. Since `resolveToolInputs` fills every over-included subBlock, this central regression guard can only over-report inputs — it cannot catch a required tool param that is only reachable through an `and:`-gated subBlock the operator isn't shown, which is exactly the class of inert-input bug this test exists to prevent. Extend the helper to evaluate the `and` clause (and honor `mode: 'advanced'`/`showWhenEnvSet`/`hideFromCopilot`) so the guard models what the editor actually renders.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
… query A present value of the wrong kind fell through to the omission branch, so `workflow_id: true` silently dropped the scope and listed every run in the repository — the same over-broad query as the dot segment, reached by a different route. A value the caller did supply must never be read as one they did not. A non-string, non-number value now fails by name; `undefined` and `null` still mean "list the whole repository", which is the documented behaviour of omitting the parameter. This mirrors `toGuardedString` in #7262, so the rebase onto the shared helper stays a clean swap.
|
@greptile review |
|
@cubic review |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
1 issue found across 15 files
Confidence score: 4/5
apps/sim/tools/github/types.ts:ListWorkflowRunsParamscannot represent numeric workflow IDs even though the tool accepts and documents them, which can block typed callers from using valid IDs; typeworkflow_idasstring | number.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/sim/tools/github/types.ts">
<violation number="1" location="apps/sim/tools/github/types.ts:1525">
P2: Numeric workflow IDs cannot be represented by `ListWorkflowRunsParams`, even though this tool documents and accepts them. Type `workflow_id` as `string | number` so typed callers can use numeric IDs.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
…ator The reachability guard hand-rolled its condition match and only looked at the top-level `operation` field, so it silently ignored `and:`. A compound-gated subBlock — `github_comment`'s `path` and `line`, which also need `commentType: 'file_comment'` — was therefore counted as visible under states where the editor does not render it, and the cast's type omitted `and`, so TypeScript could not flag the gap. That defect only ever over-reports available inputs, which is exactly the false confidence this file exists to remove: the guard could pass while a required param had no reachable field. Uses `evaluateSubBlockCondition` instead, satisfying the secondary gate before evaluating. "Can be rendered" is the right question for a reachability guard — whether an operation has any way to supply a required param, not whether one particular editor state happens to show it — and the helper now says so.
|
@greptile review |
|
@cubic review |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
1 issue found across 15 files
Confidence score: 3/5
- In
apps/sim/blocks/blocks/github.ts, the operation switch can leave the alias unset while a stale canonical value from another operation is still sent, potentially applying the wrong request parameter; filter persisted inputs to the selected operation or otherwise distinguish unset values before building the request.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/sim/blocks/blocks/github.ts">
<violation number="1" location="apps/sim/blocks/blocks/github.ts:2351">
P2: When an operation switch leaves the alias field unset, this guard lets a stale canonical value from another operation reach the request. Filter persisted inputs to fields for the selected operation, or otherwise distinguish block-state inputs from direct tool inputs before preserving the canonical value.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
| * same-named leftover (`title` from Create Issue, `sort` from a | ||
| * search) would otherwise be sent under this operation. | ||
| */ | ||
| if (!(subBlockId in params)) continue |
There was a problem hiding this comment.
P2: When an operation switch leaves the alias field unset, this guard lets a stale canonical value from another operation reach the request. Filter persisted inputs to fields for the selected operation, or otherwise distinguish block-state inputs from direct tool inputs before preserving the canonical value.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/sim/blocks/blocks/github.ts, line 2351:
<comment>When an operation switch leaves the alias field unset, this guard lets a stale canonical value from another operation reach the request. Filter persisted inputs to fields for the selected operation, or otherwise distinguish block-state inputs from direct tool inputs before preserving the canonical value.</comment>
<file context>
@@ -2279,6 +2329,44 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
+ * same-named leftover (`title` from Create Issue, `sort` from a
+ * search) would otherwise be sent under this operation.
+ */
+ if (!(subBlockId in params)) continue
+ const value = params[subBlockId]
+ mapped[toolParam] = value === '' || value === null ? undefined : value
</file context>
There was a problem hiding this comment.
Thought about this one for a while. The blank case you raised earlier is fixed; this residual is genuinely undecidable at this layer, and I'd rather say so than paper over it.
From the mapper's inputs, these two are indistinguishable:
- UI path: operator switched to Update Milestone,
milestone_titleuntouched, a staletitlelingers from Create Issue. - Agent path: the model addressed the tool param directly as
title, because the tool param name is what the LLM schema exposes.
Both arrive as "alias key absent, canonical key present". Clearing unconditionally would fix the first and break the second — and break it hardest exactly where it hurts most: content is required on both reaction tools, so dropping a model-supplied content fails the call outright. That's a worse, more frequent regression than the stale value.
What I think saves the reachable case: a subBlock the editor has rendered is in block state, so under Update Milestone milestone_title is present (blank) rather than absent, and the presence rule then clears the stale title. The leak needs milestone_title absent while title is present — the operation selected without its own form ever being rendered.
Your suggested fix — scoping persisted inputs to the active operation — is the right one, but it belongs upstream in the executor rather than in one integration's mapper: every block with per-operation fields has this shape, and solving it here would leave the other 250 untouched while adding a second, divergent notion of visibility. Happy to raise it against generic-handler if you agree that's the right home.
Leaving as-is deliberately, with the tradeoff recorded here rather than silently chosen.
The request builder accepts a number and stringifies it — an LLM tool call serializes a numeric id as a JSON number — but the param type said `string`, so a typed caller could not pass what the runtime documents and handles. Widened to `string | number`, and the test that covers the numeric form now goes through the declared type so the two cannot drift apart again.
|
@greptile review |
|
@cubic review |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
1 issue found across 15 files
Confidence score: 3/5
- In
apps/sim/tools/github/types.ts, the LLM tool path still declaresworkflow_idas a string, so callers cannot use the documented numeric form when invokinglist_workflow_runs; update the tool schema to represent numeric support.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/sim/tools/github/types.ts">
<violation number="1" location="apps/sim/tools/github/types.ts:1530">
P2: When `list_workflow_runs` is invoked through the LLM tool path, the numeric support documented here is not exposed because the tool schema still declares `workflow_id` as a string. Update the tool schema to represent the accepted string-or-number contract, or keep this interface string-only and remove the numeric guarantee.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
| * call serializes a numeric id as a JSON number, and the request builder | ||
| * stringifies it. | ||
| */ | ||
| workflow_id?: string | number |
There was a problem hiding this comment.
P2: When list_workflow_runs is invoked through the LLM tool path, the numeric support documented here is not exposed because the tool schema still declares workflow_id as a string. Update the tool schema to represent the accepted string-or-number contract, or keep this interface string-only and remove the numeric guarantee.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/sim/tools/github/types.ts, line 1530:
<comment>When `list_workflow_runs` is invoked through the LLM tool path, the numeric support documented here is not exposed because the tool schema still declares `workflow_id` as a string. Update the tool schema to represent the accepted string-or-number contract, or keep this interface string-only and remove the numeric guarantee.</comment>
<file context>
@@ -1521,6 +1521,13 @@ export interface TriggerWorkflowParams extends BaseGitHubParams {
+ * call serializes a numeric id as a JSON number, and the request builder
+ * stringifies it.
+ */
+ workflow_id?: string | number
actor?: string
branch?: string
</file context>
There was a problem hiding this comment.
Deliberate, and it mirrors an existing pattern in the repo rather than an oversight — but you're right that the two lines should agree on what they each mean, so I want to state it.
The tool type is a single JSON-schema scalar; there is no union to declare, so 'string' is the only accurate schema for a parameter GitHub documents as "the ID of the workflow — you can also pass the workflow file name as a string". Declaring 'number' instead would be worse: it would push the LLM away from the filename form, which is the one list_workflows actually surfaces via path.
The TypeScript widening is not a promise made to the model — it's tolerance for a value that arrives as a number regardless of what the schema says. #7262's toGuardedString documents exactly this, and it's the reason that helper accepts numbers at all:
Tool params are declared
type: 'string', but that declaration is not enforced anywhere before the value reaches here: it arrives from an LLM tool call or from stored workflow state, where a numeric-looking id can be serialized as a JSON number and stays one.
So 'string' in the schema and string | number in the interface are describing two different things: what the model is asked for, and what the runtime must survive. Removing the numeric tolerance would reintroduce the failure that reasoning exists to prevent — and a numeric id sent as 42 would take the '' branch and silently widen the query to the whole repository, which is the bug I fixed two commits ago.
The interface comment now says this explicitly, so the next reader doesn't have to infer it.
|
@greptile review |
|
@cubic review |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
Thread status: 15 resolved, 5 deliberately openResolved the threads where a fix actually landed, and left open the ones where I declined — those are live disagreements and the reviewer should get the last word rather than have me close my own argument. Open on purpose (5)Branch-protection replace semantics — two threads on Trimming Stale canonical value when the alias field is never rendered — Tool schema Also confirmed for the rebaseOnly one identifier in this diff is newly trimmed — The PR body now also records the two findings worth carrying forward: the |
|
Closing for now — not because of a defect. This batch grew to 17 PRs across ~700 changed call sites, and we would rather revisit it as smaller, independently testable pieces than merge this much at once. Nothing here is lost: the branch |
Nine GitHub block fields render, accept input, and are then discarded.
A subBlock binds to a tool param only when its id matches the param name (
resolveSubBlockForParamintools/params.ts), and the block declares notools.config.paramsmapper and nocanonicalParamId. So every field whose id merely resembles the param it feeds is inert: the control renders, the user fills it, and the value never reaches the request.Found while auditing the GitHub integration. Independent of #7262 — different files, either can merge first.
What was broken
Three of these make an operation impossible rather than degraded:
gist_publicpubliccreate_gistdefaultspublic ?? falsereaction_contentcontentmilestone_titletitlePlus six inert filters/options:
fork_name,fork_sort,milestone_description,milestone_state,milestone_sort, andworkflow_id.The gist one is the one I'd look at first: it silently contradicts an explicit user choice about visibility, and nothing surfaces the disagreement.
Three more, same area:
restrictionsis required by the API and had no field; the other three required params were collapsed undermode: 'advanced'and unmarked required, so the default form showed only owner/repo/branch. The object-valued ones were also declaredtype: 'string'ininputs, so the handler never parsed them and sent JSON text.check_starcould never answer "not starred". GitHub encodes this endpoint's answer in the status line with no body — 204 starred, 404 not — and the executor rejects every non-2xx beforetransformResponseruns. The negative half, the one a workflow branches on, surfaced as a tool failure.list_workflow_runsrendered a "Workflow ID or Filename" field for a tool with no such param, silently widening the query to every run in the repository.Approach
Aliases, not renames. A subBlock id is persisted workflow state, so renaming one needs a
_removed_migration.canonicalParamIdis also the wrong tool here — the repo reserves it for linking alternative inputs for one logical param and forbids reusing a subBlock id, andcontent/titleare both live ids elsewhere in the block. So: an operation-keyed alias table plus atools.config.paramsmapper.Keyed by operation, not flattened. Targets collide —
fork_sortandmilestone_sortboth feedsort— and stored block state keeps values for fields the current operation does not render, so a flat table would let a stale sibling win.Every assignment is guarded.
generic-handlermerges{ ...inputs, ...params(inputs) }, so an unconditional write clobbers a model-supplied value withundefinedon the agent path. One test pins exactly that, and another asserts the mapper never emitsundefinedfor any operation. No coercion intools.config.tool.Boolean coercion. Dropdown option ids are strings, so
'false'arrives truthy and every boolean param inverts. Coerced for the seven boolean tool params reachable from a dropdown, converting only the exact strings so an already-boolean value is untouched.nonErrorStatusesonToolConfigis opt-in and narrow: absent the declaration, nothing changes for any of the other 5,247 tools. Onlycheck_stardeclares it.Workflow id is percent-encoded.
list_workflowsreports a workflow bypath(.github/workflows/ci.yml), which is the value an agent chains in, and GitHub resolves that spelling only as%2F— a real slash 404s. Verified against the live API.Tests
Every fix is pinned by a test verified red before the fix, including a generic guard that asserts every required tool param is reachable for every operation — the check that would have caught all nine. It initially passed vacuously against
vitest.setup's mocked@/tools/registry; unmocked, it reproduces exactly the four hard failures.Note for whoever rebases
#7262 replaces raw path interpolation in these tools with the
safeUrlPath*helpers. Theworkflow_idinterpolation added here usesencodeURIComponent, which is whatsafeEncodedUrlPathSegmentdoes — it should become that helper on rebase.Verification
tools/github,blocks/,tools/index.test.ts,tools/params.test.tscheck:auditspass, includingdocs:check,tool-metadata:check,integration-catalog:check, and the subblock-ID stability checktool-metadata.tsis exactly the one new param, no sweep-in driftDeliberately excluded: the nullable
commit.commit.authordereferences, phantomstate_reason/node_idoutputs, and the unreachable non-2xx branches in seven other tools. Real, but a separate PR — this one stays reviewable.Two findings worth carrying forward
Both were defects in this PR, found in review. Recording them because each is an instance of a class, not a one-off.
Encoding is not protection — again
workflow_id: '..'silently listed every run in the repository.encodeURIComponent('..')returns'..'verbatim — a dot segment is made entirely of unreserved characters — and the WHATWG parser then removes it and pops a path segment, resolving/actions/workflows/../runsback to/actions/runs.So the parameter added to narrow the query produced exactly the over-broad query it was meant to prevent, and did it silently. This is the third time in this batch that encoding was mistaken for protection, and it happened inside the PR fixing that class. The rule holds without exception: only value rejection neutralizes a dot segment. A dot segment inside a longer value is a different case and stays accepted — its separators survive as
%2Fand the parser does not decode those first.A present-but-wrong-typed value (
workflow_id: true) reached the same over-broad query through the omission branch, and is rejected for the same reason: a value the caller did supply must never be read as one they did not.A test that could not fail
The
never emits an undefined value for any aliastest asserted over a map that was always empty —mapParams({ operation })returns{}when no alias has a source, so the innerexpectnever executed for any operation. It passed unconditionally and gave false confidence about the exact property it named.This is the ninth instance of the vacuous-assertion pattern across this effort, so it is a known class rather than a slip. Two shapes recurred here specifically:
vitest.setup's mocked@/tools/registry— every tool resolved toundefinedand was skipped. It neededvi.unmock('@/tools/registry')before it reproduced the four real failures.The check that catches both: revert the fix and confirm the test goes red. Every fix in this PR was verified that way.
Path-safety note for the rebase
Only one identifier in this diff is newly trimmed —
workflow_id— andlist_workflow_runsis aGET, so it belongs on the trimming helper (safeEncodedUrlPathSegment), not onstrictUrlPathSegment, which #7262 added to refuse padded identifiers on state-changing requests. No mutating request in this diff interpolates a newly-trimmed identifier:check_staris aGETand its URL is untouched, andupdate_branch_protection's URL is untouched here (its.trim()is a blankness test on a body field that returns the original value unmodified).