You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 2ee88cb
Browse filesBrowse the repository at this point in the historyBrowse files
fix(pm): refuse --tier against --commands/--json instead of silently preferring the tier (#14765)
`--tier` is a third shape of stdout and was the one the "two spellings of
stdout" rule did not name. `mode` was computed from --commands/--json and
then discarded by the `--tier` branch, so the tier prose landed in a stream
whose caption promises one runnable command per line, or one JSON document —
at exit 0, with nothing on either stream saying the flag had been dropped.
Both pairs now refuse with the shape the existing refusals use: exit 2, one
`dispatch-gates: …` line on stderr naming both flags. Ordered after the
`--commands --json` pair so all three together keep the message they had.
`--tier` on its own is byte-identical to before.
Fixes#14294
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
Co-authored-by: Claude <noreply@anthropic.com>
t('⭐ --tier against --commands refuses instead of silently preferring the tier', tierCmdRun.status === 2 && (tierCmdRun.stdout ?? '').trim() === '');
17420
+
t('and its refusal names BOTH flags, so the dropped one is never left to be guessed', (tierCmdRun.stderr ?? '').includes('--tier') && (tierCmdRun.stderr ?? '').includes('--commands'));
t('⭐ --tier against --json refuses the same way, on the other machine-readable shape', tierJsonRun.status === 2 && (tierJsonRun.stdout ?? '').trim() === '');
17423
+
t('and that refusal names --json, never the flag this run is not about', (tierJsonRun.stderr ?? '').includes('--json') && !(tierJsonRun.stderr ?? '').includes('--commands'));
17424
+
// CONTROL: green before this fix and after it. The new branch is ordered
17425
+
// after the pair, so all three flags together still answer with the pair's
17426
+
// own message — the fix adds a refusal and rewords none.
t('CONTROL: all three together keep the pair rule that was already enforced', allThreeRun.status === 2 && (allThreeRun.stderr ?? '').includes('--commands and --json'));
17410
17429
}
17411
17430
17412
17431
// ── END TO END: the CI-measured family, on the card it was measured on (#14004)
@@ -17792,9 +17811,36 @@ if (invokedDirectly) {
17792
17811
// Two answers to "what shape is stdout" is no answer. Blending them — or
17793
17812
// silently preferring one — is the class of failure this whole file is
17794
17813
// about, and it would be a poor place to commit it: these two flags exist
17795
-
// because a consumer could not tell what it was reading.
17814
+
// because a consumer could not tell what it was reading. The rule is about
17815
+
// how many answers the stream carries, not about which two flags happened
17816
+
// to be named when it was written: `--tier` is a THIRD shape of that same
17817
+
// stdout, and it is refused against each of these two in the branch below.
17796
17818
console.error('dispatch-gates: --commands and --json are two spellings of stdout — pass one.');
17797
17819
process.exit(2);
17820
+
} else if (process.argv.includes('--tier') && (process.argv.includes('--commands') || process.argv.includes('--json'))) {
17821
+
// The third shape, and the one the pair above did not reach: `mode` was
17822
+
// computed from --commands/--json and then DISCARDED by the `--tier` branch
17823
+
// at the bottom of this block, so the tier PROSE landed in a stream whose
17824
+
// caption promises one runnable command per line, or one JSON document —
17825
+
// at exit 0, with nothing on either stream saying the flag had been
17826
+
// dropped. That is the silent preference the rule above forbids, committed
17827
+
// one flag over from where it is enforced.
17828
+
//
17829
+
// REFUSED, not re-routed. Sending the tier verdict to stderr whenever a
17830
+
// machine-readable mode is asked for is defensible — `--tier` answers a
17831
+
// different question and reads no tree — but it changes what `--tier`
17832
+
// MEANS when combined, and that is a design call for this file's owner,
17833
+
// not a repair to a silent-drop bug. Same shape as the two refusals above
17834
+
// it and the `--ran --tier` refusal above them: one line, exit 2, both
17835
+
// flags named so the caller never has to guess which one was dropped.
17836
+
//
17837
+
// Ordered AFTER the pair deliberately: `--tier --commands --json` keeps
17838
+
// the message it already had, so this branch adds a refusal and changes
17839
+
// none.
17840
+
console.error(
17841
+
`dispatch-gates: --tier and ${process.argv.includes('--commands') ? '--commands' : '--json'} are two spellings of stdout — pass one.`,
17842
+
);
17843
+
process.exit(2);
17798
17844
} else if (wantsChanged && argvPaths.length > 0) {
17799
17845
// The two input modes answer different questions and must never be blended:
17800
17846
// silently preferring one would make the other's arguments vanish without a
@@ -17862,7 +17908,7 @@ if (invokedDirectly) {
17862
17908
derived = changedPathsFromGit();
17863
17909
} catch (err) {
17864
17910
console.error(`dispatch-gates: could not derive the change set — ${err.message}`);
0 commit comments