fix: normalize command paths to base names in shellparse (#25599)

Normalize program names in shellparse.Parse to their basename.

Does not rely on filepath.Base because the server may run on either
Linux or Windows where the behavior would differ.

Closes CODAGT-470
This commit is contained in:
Mathias Fredriksson
2026-05-22 13:36:53 +03:00
committed by GitHub
parent 5d40bac79f
commit 0ba702c43f
8 changed files with 187 additions and 159 deletions
+1 -1
View File
@@ -16825,7 +16825,7 @@ const docTemplate = `{
"type": "string"
},
"parsed_commands": {
"description": "ParsedCommands holds parsed programs from an execute tool call's\nshell command, one entry per simple command in source order. Each\nentry is [program] or [program, arg] where arg is the first non-flag\npositional argument. Only populated when ToolName is \"execute\" and\nthe command parses successfully; nil otherwise.",
"description": "ParsedCommands holds parsed programs from an execute tool call's\nshell command, one entry per simple command in source order. Each\nentry is [program] or [program, arg] where arg is the first non-flag\npositional argument. Program names are normalized to their base\nname (e.g. /usr/bin/go becomes go). Only populated when ToolName\nis \"execute\" and the command parses successfully; nil otherwise.",
"type": "array",
"items": {
"type": "array",
+1 -1
View File
@@ -15169,7 +15169,7 @@
"type": "string"
},
"parsed_commands": {
"description": "ParsedCommands holds parsed programs from an execute tool call's\nshell command, one entry per simple command in source order. Each\nentry is [program] or [program, arg] where arg is the first non-flag\npositional argument. Only populated when ToolName is \"execute\" and\nthe command parses successfully; nil otherwise.",
"description": "ParsedCommands holds parsed programs from an execute tool call's\nshell command, one entry per simple command in source order. Each\nentry is [program] or [program, arg] where arg is the first non-flag\npositional argument. Program names are normalized to their base\nname (e.g. /usr/bin/go becomes go). Only populated when ToolName\nis \"execute\" and the command parses successfully; nil otherwise.",
"type": "array",
"items": {
"type": "array",
+13 -2
View File
@@ -9,7 +9,8 @@ import (
// Parse returns one slice per simple command in src, in source order.
// Each is [program] or [program, arg], where arg is the first non-flag
// positional argument.
// positional argument. Program names are normalized to their base name
// (e.g. /usr/bin/go becomes go).
//
// Some malformed inputs (e.g. trailing unterminated tokens after valid
// semicolon-separated commands) yield partial results alongside a
@@ -35,7 +36,7 @@ func Parse(src string) ([][]string, error) {
if prog == "" {
return true
}
step := []string{prog}
step := []string{cmdBase(prog)}
if arg := firstNonFlagLiteral(call.Args[1:]); arg != "" {
step = append(step, arg)
}
@@ -77,6 +78,16 @@ func wordLiteral(w *syntax.Word) string {
return sb.String()
}
// cmdBase returns the base name of a command path, handling both
// forward and back slashes since commands may originate from Windows
// workspaces while this code runs on a Linux server.
func cmdBase(prog string) string {
if i := strings.LastIndexAny(prog, `/\`); i >= 0 {
return prog[i+1:]
}
return prog
}
// firstNonFlagLiteral returns the literal value of the first word in
// ws that does not start with "-", or "" if none qualifies.
//
+16 -1
View File
@@ -93,7 +93,22 @@ done`,
{
name: "quoted-program-name",
in: `"/usr/bin/git" pull`,
want: [][]string{{"/usr/bin/git", "pull"}},
want: [][]string{{"git", "pull"}},
},
{
name: "absolute-path-binary",
in: `/opt/mise/data/installs/go/1.26.2/bin/go test ./...`,
want: [][]string{{"go", "test"}},
},
{
name: "relative-path-binary",
in: `./build.sh --verbose`,
want: [][]string{{"build.sh"}},
},
{
name: "windows-path-binary",
in: `'C:\Program Files\Go\bin\go.exe' test ./...`,
want: [][]string{{"go.exe", "test"}},
},
{
name: "double-quoted-with-variable-expansion-skipped",
+3 -2
View File
@@ -259,8 +259,9 @@ type ChatMessagePart struct {
// ParsedCommands holds parsed programs from an execute tool call's
// shell command, one entry per simple command in source order. Each
// entry is [program] or [program, arg] where arg is the first non-flag
// positional argument. Only populated when ToolName is "execute" and
// the command parses successfully; nil otherwise.
// positional argument. Program names are normalized to their base
// name (e.g. /usr/bin/go becomes go). Only populated when ToolName
// is "execute" and the command parses successfully; nil otherwise.
ParsedCommands [][]string `json:"parsed_commands,omitempty" variants:"tool-call?"`
Result json.RawMessage `json:"result,omitempty" variants:"tool-result?"`
ResultDelta string `json:"result_delta,omitempty" variants:"tool-result?"`
+2 -2
View File
@@ -181,7 +181,7 @@ Experimental: this endpoint is subject to change.
Status Code **200**
| Name | Type | Required | Restrictions | Description |
|-----------------------------------|------------------------------------------------------------------------|----------|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|-----------------------------------|------------------------------------------------------------------------|----------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `[array item]` | array | false | | |
| `» agent_id` | string(uuid) | false | | |
| `» archived` | boolean | false | | |
@@ -255,7 +255,7 @@ Status Code **200**
| `»»» valid` | boolean | false | | Valid is true if UUID is not NULL |
| `»» media_type` | string | false | | |
| `»» name` | string | false | | |
| `»» parsed_commands` | array | false | | Parsed commands holds parsed programs from an execute tool call's shell command, one entry per simple command in source order. Each entry is [program] or [program, arg] where arg is the first non-flag positional argument. Only populated when ToolName is "execute" and the command parses successfully; nil otherwise. |
| `»» parsed_commands` | array | false | | Parsed commands holds parsed programs from an execute tool call's shell command, one entry per simple command in source order. Each entry is [program] or [program, arg] where arg is the first non-flag positional argument. Program names are normalized to their base name (e.g. /usr/bin/go becomes go). Only populated when ToolName is "execute" and the command parses successfully; nil otherwise. |
| `»» provider_executed` | boolean | false | | Provider executed indicates the tool call was executed by the provider (e.g. Anthropic computer use). |
| `»» provider_metadata` | array | false | | Provider metadata holds provider-specific response metadata (e.g. Anthropic cache control hints) as raw JSON. Internal only: stripped by db2sdk before API responses. |
| `»» result` | array | false | | |
+2 -2
View File
@@ -3027,7 +3027,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
### Properties
| Name | Type | Required | Restrictions | Description |
|--------------------------------|--------------------------------------------------------------|----------|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|--------------------------------|--------------------------------------------------------------|----------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `args` | array of integer | false | | |
| `args_delta` | string | false | | |
| `completed_at` | string | false | | Completed at is the time a reasoning part finished streaming, so reasoning duration can be computed as completed_at minus created_at. For interrupted reasoning, this is the interruption time. Absent when reasoning timestamp data was not recorded (e.g. messages persisted before this feature was added). |
@@ -3049,7 +3049,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
| `mcp_server_config_id` | [uuid.NullUUID](#uuidnulluuid) | false | | |
| `media_type` | string | false | | |
| `name` | string | false | | |
| `parsed_commands` | array of array | false | | Parsed commands holds parsed programs from an execute tool call's shell command, one entry per simple command in source order. Each entry is [program] or [program, arg] where arg is the first non-flag positional argument. Only populated when ToolName is "execute" and the command parses successfully; nil otherwise. |
| `parsed_commands` | array of array | false | | Parsed commands holds parsed programs from an execute tool call's shell command, one entry per simple command in source order. Each entry is [program] or [program, arg] where arg is the first non-flag positional argument. Program names are normalized to their base name (e.g. /usr/bin/go becomes go). Only populated when ToolName is "execute" and the command parses successfully; nil otherwise. |
| `provider_executed` | boolean | false | | Provider executed indicates the tool call was executed by the provider (e.g. Anthropic computer use). |
| `provider_metadata` | array of integer | false | | Provider metadata holds provider-specific response metadata (e.g. Anthropic cache control hints) as raw JSON. Internal only: stripped by db2sdk before API responses. |
| `result` | array of integer | false | | |
+3 -2
View File
@@ -2875,8 +2875,9 @@ export interface ChatToolCallPart {
* ParsedCommands holds parsed programs from an execute tool call's
* shell command, one entry per simple command in source order. Each
* entry is [program] or [program, arg] where arg is the first non-flag
* positional argument. Only populated when ToolName is "execute" and
* the command parses successfully; nil otherwise.
* positional argument. Program names are normalized to their base
* name (e.g. /usr/bin/go becomes go). Only populated when ToolName
* is "execute" and the command parses successfully; nil otherwise.
*/
readonly parsed_commands?: readonly string[][];
/**