Repository navigation
Require the command path to open the command line - #17
Merged
Merged
Conversation
AI agent hosts such as Claude Code and opencode decide whether an agent may run a command by matching its text against allow, ask, and deny rules. A prefix rule is reliable only if the command path always opens the command line, and no rule can tell a writing command from a reading one by its action name: action names come from the REST API and form an open set, while Get Work Items Batch and Query By Wiql are POST requests that only read. The brief therefore requires flags to follow the command path, one form per command, and local command names distinct from API areas. Future write operations are gated by --allow-changes, and irreversible ones by --allow-irreversible in addition, so that one ask rule covers every change and one deny rule excludes the irreversible ones. The gate flags have no environment variable or config key, since a setting outside the command line is invisible to the rules. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCREyMqPZyHxuCRoy5FZqf
Cobra accepts flags at any position, so keeping the command path at the start of the command line needs an explicit check in run, located with root.Find. A root PersistentPreRunE was rejected: cobra runs only the nearest such hook, and a child command defining its own would silently drop the check. Flags cobra handles itself (--help, --version) and flags the target command does not define are left to cobra. The notes for future evolution record how writing commands will be marked, so that the command tree test can require the gate flags of exactly those commands. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCREyMqPZyHxuCRoy5FZqf
The rules are listed among the non-negotiable constraints so that a future change is checked against them before it is made, with the full wording left to the brief. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCREyMqPZyHxuCRoy5FZqf
An agent permission rule such as "tfscli wit work-items get *" matches the command text by its prefix, so "tfscli --verbose wit work-items get 1" slipped past it. run now locates the target command with root.Find and refuses a flag token before the last word of its path with a config error that shows the corrected command, before any request is sent. The check yields to cobra wherever it cannot place an argument: an unknown command, a flag the target command does not define (--help and --version included, since cobra adds them only on execution), the "--" terminator, and a stray positional argument. Moving an unknown flag would not make the command valid, so cobra's own error is the better one. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCREyMqPZyHxuCRoy5FZqf
Agent permission rules match the command text, so a second name for a command would let it run under a form no rule was written for. The rule is recorded in the brief; this test makes make check enforce it for every command in the tree. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCREyMqPZyHxuCRoy5FZqf
The README states that flags follow the command and gives ready rules for Claude Code and opencode that allow the reading commands and deny auth, which an agent has no reason to run. The opencode rules say only what its documentation does: the last matching rule wins. The agent skill tells the agent to keep flags after the command rather than rely on the error, and the changelog records the refusal as a change to the command line. Closes docs/archive/2026-10-08-tasks-command-shape-permissions.md Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCREyMqPZyHxuCRoy5FZqf
cobra's Find decides whether a flag takes a value only from the flags of the command it stands on. A flag unknown there swallows the next word: --help before cobra registers it, or a flag of an action placed at the level of its group. "tfscli --help wit work-items get" failed with an unknown command, and "tfscli wit wiql --time-precision query-by-wiql" printed the help of wiql with exit 0 without sending the query, a false success the flag order check let through because Find stopped at the group. The check now walks the arguments from the root and decides whether a flag takes a value by a flag of that name on the current command or anywhere below it, so it always reaches the real command. Every flag before the end of the path is refused, --help included; --version, which only the root defines, gets a message of its own. help and completion are registered when the tree is built, so they are walked like any other command rather than exempted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCREyMqPZyHxuCRoy5FZqf
With the command path found by walking the arguments, --help follows the path like any other flag, and help and completion are ordinary commands, so the brief, the README, and the agent skill no longer list exceptions; only an invocation without a path, such as tfscli --help, has none. The architecture describes the walk and why cobra's Find is not used. The changelog records the --help case and, as a fix, the false success of a flag placed before its action. Closes docs/archive/2026-10-08-tasks-command-path-walk.md Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCREyMqPZyHxuCRoy5FZqf
The walk added in the previous change is a second parser of the command
line, and it diverged from cobra's in edge cases a review found. "tfscli
wit work-items --fields -p get 5" passed the check and sent the request:
the walk took the value -p for a new flag and stopped at work-items. It
also stopped at words cobra skips ("" and "-") and at a word cobra takes
as the value of a flag it does not know ("--help X"), while cobra went on
to run the command behind them.
The walk now reads a pending value before anything else, as pflag does,
and the check locates the command both by the walk and by Find and holds
to the deeper of the two: each catches the other stopping early. The
command passes only when its path occupies exactly the first words.
The message no longer lists the misplaced flags, since a stray word is
not a flag and an attached short value made the list inaccurate; the
corrected command shows what moves. A flag the command does not define
is reported as unknown in pflag's wording, rather than moved into a
command that would fail the same way. The corrected command is quoted
for the shell in single quotes, which bash and PowerShell read alike.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XA4Hx4kZtgobXtNCEpuiTC
The README said a call through tfscli.exe or a full path is asked about, which holds for Claude Code but not for opencode, where a command matching no rule is allowed unless a catch-all ask is set; it now says so, and that the deny rule guards the usual form of the call rather than being a security boundary. Its examples gain an ask rule on --allow-changes, since future write commands will fall under the same "tfscli wit *" allow rule as the reading ones. CONTRIBUTING repeats the brief's constraints for contributors who do not open it, and now lists the command line rules and the gate flags as CLAUDE.md does. The architecture describes the cross-check with Find, unknown flags, and the quoting of the corrected command. Closes docs/archive/2026-10-09-tasks-flag-order-review.md Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XA4Hx4kZtgobXtNCEpuiTC
The check located the command twice, by a walk that repeated pflag's rules for flag values and by cobra's Find, and took the deeper result. Both defects found so far were divergences between that walk and pflag. The shape of the command tree makes flag values irrelevant: a command with subcommands only groups them, taking no argument and defining no flag of its own, so in a valid command line no word naming a subcommand can follow the path. The command is now the one reached by taking, in order, every word that names a subcommand of the command reached so far. That reaches at least as deep as Find, which descends only through such words, so the walk over flag values and the cross-check are gone; every case of the existing tests passes unchanged. The rule now depends on the shape of the tree, so the command tree test requires it of every group. It found at once that auth was built as a plain cobra command rather than with newGroupCmd, and that "tfscli auth bogus" printed the help with exit 0; auth is now built like the other groups and reports an unknown command. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XA4Hx4kZtgobXtNCEpuiTC
The flag order check now relies on the shape of the command tree, so the shape becomes a rule of the brief, repeated in CLAUDE.md and CONTRIBUTING: a command with subcommands takes no arguments, defines no flags of its own, and only prints its help. A group that also acts on its own, as git stash does, is excluded, since a rule written for the group would cover every command below it. The architecture describes the check without the walk over flag values and records why that walk was rejected. The changelog records the fix to unknown commands under auth. Closes docs/archive/2026-10-09-tasks-simple-path-rule.md Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XA4Hx4kZtgobXtNCEpuiTC
Cobra adds its hidden __complete command only when it executes the tree, so the check took "tfscli __complete wit ''" for a misplaced path and broke shell completion. Such a request runs no command. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XA4Hx4kZtgobXtNCEpuiTC
A test runs the check and cobra over every command line of up to three words built from the names and flags of the tree, and over a fixed-seed sample of longer lines: a line the check accepts must make cobra select a command whose path opens the line, and the corrected command proposed for a refused line must itself be accepted and select the command the message names. The test found three divergences, now fixed: - "--" no longer stops the search for the path, since pflag takes it for the value of a flag before it, as in "tfscli --api-version -- completion"; - a flag before "__complete" is refused like any other, the completion request being taken for a subcommand of the root that ends the path; - the path is moved to the front until it stays there, so that the correction of "tfscli - login auth" is "tfscli auth login -" rather than a line refused again. Closes docs/archive/2026-10-09-tasks-flag-order-oracle.md Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XA4Hx4kZtgobXtNCEpuiTC
- Read the words before the path against the final path: when the path was found in more than one pass, flags before it went unchecked, and "tfscli work-items wit --bogus get 5" was told to move --bogus after the path instead of being told it is unknown. - Read "--" before the path as pflag does, as the end of the flags rather than as an unknown flag named "--"; report "--=x" and "---x" as bad flag syntax; read a group of shorthands letter by letter; and refuse --version only where pflag would read it as a flag. - Refuse cobra's case-insensitive command matching in the tree test. - Extend the oracle test to refusals for a flag before the path, give each worker one tree, keep completion requests from writing cobra's diagnostics to stderr, and report failures in a stable order; turn off cobra's Explorer check for the whole package in TestMain. - Narrow the group rule in CLAUDE.md and CONTRIBUTING.md to areas, resources, and local groups; describe the quoting, the reading of the words before the path, and the oracle in docs/architecture.md. - Merge the changelog entry on --time-precision into the entry on the flag order; in README, put the opencode deny rule last and state that the rules govern the usual form of a call, not a determined agent. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XA4Hx4kZtgobXtNCEpuiTC
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVURGFoSzFiQP2uJD8fLu7
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVURGFoSzFiQP2uJD8fLu7
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVURGFoSzFiQP2uJD8fLu7
…tion check (TASK-04) The wider oracle alphabet showed that cobra stops at the first flag value it cannot parse, which the check does not read, so the oracle now accepts that outcome instead of expecting a later flag to be reported. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVURGFoSzFiQP2uJD8fLu7
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVURGFoSzFiQP2uJD8fLu7
The ask rule on --allow-changes matched any command carrying the string, so it also prompted for unrelated commands such as a search or a commit message that mentions the flag; it now starts with tfscli like the other rules. The section states the rules and the precedence they need independently of an agent, and keeps the Claude Code and opencode settings as examples whose format belongs to the agent. The section is unwrapped to match the rest of the file. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HmjswYSvuZ7yb3erKTWJH3
The list repeats the brief to catch a violation without opening it, so it names each rule and leaves the detail to the brief. The command line rule keeps every part but drops the parenthetical expansions, which tests in internal/cli already enforce in part. The gate flags shrink to a clause of the read-only item: no write operation is added without a product decision, which means reading the brief anyway. The note on the gate flags in the configuration precedence item repeated that item and is removed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HmjswYSvuZ7yb3erKTWJH3
The cli entry in architecture.md retold the flag order check line by line: the quoting for bash and PowerShell, the repeated move of the path, cobra's completion command, pflag's wording, the details of the oracle test. The comments in internal/cli/flagorder.go already say all of it next to the code, so the entry keeps what the code does not: the check and why it needs no flag values, the tests that guard the tree shape, and the rejected alternatives. Both data flows now start with the check. The brief keeps the product decisions and drops cobra internals: the hidden completion command becomes one sentence on shell completion, and the OPTIONS detail of the gate, already in the architecture, goes. The gate flags are said to stand outside the configuration chain once, in "Configuration". Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HmjswYSvuZ7yb3erKTWJH3
The tasks skill described itself as the workflow for any work that starts now, so an agent asked to fix the findings of a branch review planned the fixes as a tasks file. This branch gained five such files on top of the one for the feature itself, each with a two-task plan that was never agreed with anyone, and TASK-01 and TASK-02 now refer to several files in one branch's history. A tasks file is now reserved for work that needs a plan: several tasks, a breakdown agreed with the user before the work starts, or work that may outlast one session. Review fixes, single fixes, and small requested changes are done directly and explained in their commit messages, even when the user leaves the details to the agent. A fix that turns into a change of approach gets a tasks file once the user has approved the decision. The rule is in the skill rather than in CONTRIBUTING.md because it does not depend on the project; the global copy of the skill carries the same text. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015c62YtYEvUtpS8VvRmvRb4
CLAUDE.md is loaded into every agent session, so it names each rule and leaves the detail to the brief, as CONTRIBUTING does. The gate flags shrink to a clause of the read-only item, and the command line rule drops the expansions that the command tree tests enforce. The gate flags are no longer mentioned in the configuration precedence item, and the command shape section no longer repeats that flags follow the action. That no flag gives a reading command a side effect is a rule of the command line, in force before any write operation exists, and was lost from CONTRIBUTING together with the gate details; both files now state it with the other command line rules. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HmjswYSvuZ7yb3erKTWJH3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
AI agent hosts such as Claude Code and opencode allow or deny a shell command by matching its text, usually as a prefix (
tfscli wit work-items get *). Cobra accepts flags anywhere, sotfscli --verbose wit work-items get 1did not match a rule written for the command. A flag of an action placed before the action, as intfscli wit wiql --time-precision query-by-wiql, also printed the group's help and exited 0 without running the query.Approach
Before executing the tree,
runlocates the command and refuses the line with aconfigerror unless the command path occupies the first words. The error shows the corrected command. The path is found by taking every word that names a subcommand, without reading flag values. An earlier version parsed flag values, but each divergence from pflag let a command run with its path in the wrong place. Not parsing flags is sound only if the tree keeps a fixed shape:Tests enforce this shape. An oracle test checks the rule against cobra over every command line of up to three words built from the tree's names and flags, and over a fixed-seed sample of longer lines.
The brief now records these command line rules. It also records the future gate flags for write operations (
--allow-changes,--allow-irreversible), so that the rules hold once write commands are added.Notes
--helpincluded, is now refused.tfscli --helpandtfscli --versionwithout a path still work.__completecommand is the one exception. The words after it are the line being completed, not a command that runs.authorcompletionis now an error instead of help with exit code 0.