Skip to content

Commit 12422cd

Browse files
committed
fix: use --flag=value syntax for variadic CLI flags
Variadic flags like --allowed-tools, --add-dir, --tools consume all following positional arguments. This caused the prompt to be eaten when using these flags in mdflow agents. Solution: Use --flag=value syntax instead of --flag value for variadic flags, ensuring the prompt is correctly passed as a positional argument. Supported formats in frontmatter: - String: allowed-tools: "Read,Edit" - Array: allowed-tools: [Read, Edit, "Bash(git:*)"]
1 parent 0c1e912 commit 12422cd

File tree

5 files changed

+113
-20
lines changed

5 files changed

+113
-20
lines changed

src/command-builder.test.ts

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,26 @@ describe("buildArgsFromFrontmatter", () => {
7777

7878
describe("array values", () => {
7979
it("repeats flag for each array element", () => {
80+
// Non-variadic arrays use space-separated format
81+
const result = buildArgsFromFrontmatter(
82+
{ "include": ["./src", "./tests"] },
83+
new Set()
84+
);
85+
expect(result).toEqual([
86+
"--include", "./src",
87+
"--include", "./tests",
88+
]);
89+
});
90+
91+
it("variadic flags use = syntax for arrays", () => {
92+
// add-dir is a variadic flag, so it uses --flag=value format
8093
const result = buildArgsFromFrontmatter(
8194
{ "add-dir": ["./src", "./tests"] },
8295
new Set()
8396
);
8497
expect(result).toEqual([
85-
"--add-dir", "./src",
86-
"--add-dir", "./tests",
98+
"--add-dir=./src",
99+
"--add-dir=./tests",
87100
]);
88101
});
89102

@@ -659,9 +672,9 @@ describe("buildCommand", () => {
659672
expect(result.args).toContain("opus");
660673
expect(result.args).toContain("--verbose");
661674
expect(result.args).not.toContain("--debug");
662-
expect(result.args).toContain("--add-dir");
663-
expect(result.args).toContain("./src");
664-
expect(result.args).toContain("./tests");
675+
// add-dir is a variadic flag, so it uses --flag=value format
676+
expect(result.args).toContain("--add-dir=./src");
677+
expect(result.args).toContain("--add-dir=./tests");
665678

666679
// Positional with mapping goes to positionals
667680
expect(result.positionals).toContain("--prompt");
@@ -1014,12 +1027,12 @@ describe("integration scenarios", () => {
10141027
"/workspace"
10151028
);
10161029

1017-
expect(result.args).toContain("--add-dir");
1018-
const addDirCount = result.args.filter(a => a === "--add-dir").length;
1030+
// add-dir is a variadic flag, so it uses --flag=value format
1031+
expect(result.args).toContain("--add-dir=./src");
1032+
expect(result.args).toContain("--add-dir=./lib");
1033+
expect(result.args).toContain("--add-dir=./tests");
1034+
const addDirCount = result.args.filter(a => a.startsWith("--add-dir=")).length;
10191035
expect(addDirCount).toBe(3);
1020-
expect(result.args).toContain("./src");
1021-
expect(result.args).toContain("./lib");
1022-
expect(result.args).toContain("./tests");
10231036
expect(result.args).toContain("--dangerously-skip-permissions");
10241037
});
10251038

@@ -1203,10 +1216,10 @@ describe("dry-run consistency guarantee", () => {
12031216
expect(spawnArgs).toContain("--prompt");
12041217
expect(display).toContain("--prompt");
12051218

1206-
// Verify array flags are handled consistently
1207-
const addDirCount = spawnArgs.filter(a => a === "--add-dir").length;
1219+
// Verify array flags are handled consistently (variadic flags use --flag=value)
1220+
const addDirCount = spawnArgs.filter(a => a.startsWith("--add-dir=")).length;
12081221
expect(addDirCount).toBe(2);
1209-
expect(display.split("--add-dir").length - 1).toBe(2);
1222+
expect(display.split("--add-dir=").length - 1).toBe(2);
12101223

12111224
// Verify _env is separate (not in args)
12121225
expect(spawnArgs).not.toContain("API_KEY");

src/command-builder.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,22 @@ function isPositionalKey(key: string): boolean {
6565
return /^\$\d+$/.test(key);
6666
}
6767

68+
/**
69+
* Variadic flags that consume all following positional arguments.
70+
* These must use --flag=value syntax to avoid eating the prompt.
71+
*/
72+
const VARIADIC_FLAGS = new Set([
73+
"allowed-tools",
74+
"allowedTools",
75+
"disallowed-tools",
76+
"disallowedTools",
77+
"tools",
78+
"add-dir",
79+
"betas",
80+
"mcp-config",
81+
"plugin-dir",
82+
]);
83+
6884
/**
6985
* Convert frontmatter key to CLI flag
7086
* e.g., "model" -> "--model"
@@ -123,13 +139,23 @@ export function buildArgsFromFrontmatter(
123139
// Array -> repeat flag for each value
124140
if (Array.isArray(value)) {
125141
for (const v of value) {
126-
args.push(toFlag(key), String(v));
142+
// Variadic flags need --flag=value syntax to not eat following args
143+
if (VARIADIC_FLAGS.has(key)) {
144+
args.push(`${toFlag(key)}=${String(v)}`);
145+
} else {
146+
args.push(toFlag(key), String(v));
147+
}
127148
}
128149
continue;
129150
}
130151

131152
// String/number -> flag with value
132-
args.push(toFlag(key), String(value));
153+
// Variadic flags need --flag=value syntax to not eat following args
154+
if (VARIADIC_FLAGS.has(key)) {
155+
args.push(`${toFlag(key)}=${String(value)}`);
156+
} else {
157+
args.push(toFlag(key), String(value));
158+
}
133159
}
134160

135161
return args;

src/command.test.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,34 @@ describe("buildArgs", () => {
5353
});
5454

5555
test("handles arrays by repeating flags", () => {
56+
// Non-variadic arrays use space-separated format
57+
const result = buildArgs({ "include": ["./src", "./tests"] }, new Set());
58+
expect(result).toEqual(["--include", "./src", "--include", "./tests"]);
59+
});
60+
61+
test("variadic flags use = syntax to avoid eating positional args", () => {
62+
// add-dir is a variadic flag, so it uses --flag=value format
5663
const result = buildArgs({ "add-dir": ["./src", "./tests"] }, new Set());
57-
expect(result).toEqual(["--add-dir", "./src", "--add-dir", "./tests"]);
64+
expect(result).toEqual(["--add-dir=./src", "--add-dir=./tests"]);
65+
});
66+
67+
test("variadic allowed-tools string uses = syntax", () => {
68+
const result = buildArgs({ "allowed-tools": "Bash(git status:*)" }, new Set());
69+
expect(result).toEqual(["--allowed-tools=Bash(git status:*)"]);
70+
});
71+
72+
test("variadic allowed-tools array produces multiple --flag= entries", () => {
73+
const result = buildArgs({ "allowed-tools": ["Read", "Edit", "Bash(git:*)"] }, new Set());
74+
expect(result).toEqual([
75+
"--allowed-tools=Read",
76+
"--allowed-tools=Edit",
77+
"--allowed-tools=Bash(git:*)"
78+
]);
79+
});
80+
81+
test("variadic allowed-tools comma-separated string works", () => {
82+
const result = buildArgs({ "allowed-tools": "Read,Edit,Bash" }, new Set());
83+
expect(result).toEqual(["--allowed-tools=Read,Edit,Bash"]);
5884
});
5985

6086
test("skips system keys (_inputs)", () => {

src/command.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,22 @@ function isPositionalKey(key: string): boolean {
9898
return /^\$\d+$/.test(key);
9999
}
100100

101+
/**
102+
* Variadic flags that consume all following positional arguments.
103+
* These must use --flag=value syntax to avoid eating the prompt.
104+
*/
105+
const VARIADIC_FLAGS = new Set([
106+
"allowed-tools",
107+
"allowedTools",
108+
"disallowed-tools",
109+
"disallowedTools",
110+
"tools",
111+
"add-dir",
112+
"betas",
113+
"mcp-config",
114+
"plugin-dir",
115+
]);
116+
101117
/**
102118
* Extract command from filename
103119
* e.g., "commit.claude.md" → "claude"
@@ -187,13 +203,23 @@ export function buildArgs(
187203
// Array → repeat flag for each value
188204
if (Array.isArray(value)) {
189205
for (const v of value) {
190-
args.push(toFlag(key), String(v));
206+
// Variadic flags need --flag=value syntax to not eat following args
207+
if (VARIADIC_FLAGS.has(key)) {
208+
args.push(`${toFlag(key)}=${String(v)}`);
209+
} else {
210+
args.push(toFlag(key), String(v));
211+
}
191212
}
192213
continue;
193214
}
194215

195216
// String/number → flag with value
196-
args.push(toFlag(key), String(value));
217+
// Variadic flags need --flag=value syntax to not eat following args
218+
if (VARIADIC_FLAGS.has(key)) {
219+
args.push(`${toFlag(key)}=${String(value)}`);
220+
} else {
221+
args.push(toFlag(key), String(value));
222+
}
197223
}
198224

199225
return args;

src/integration.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@ describe("integration: command building", () => {
3232
expect(args).toContain("--model");
3333
expect(args).toContain("opus");
3434
expect(args).toContain("--print");
35-
expect(args).toContain("--add-dir");
36-
expect(args.filter(a => a === "--add-dir")).toHaveLength(2);
35+
// add-dir is a variadic flag, so it uses --flag=value format
36+
expect(args).toContain("--add-dir=./src");
37+
expect(args).toContain("--add-dir=./tests");
38+
expect(args.filter(a => a.startsWith("--add-dir="))).toHaveLength(2);
3739
});
3840

3941
it("excludes system keys from args", () => {

0 commit comments

Comments
 (0)