Summary
The shell approval matcher treats a bare filename as part of the verb.
The defect affects Netclaw 0.25.2 and the current dev branch.
Reproduction
Assume the command current directory is /home/alice/repo.
Run this command through shell_execute:
git diff install-skills.sh
Select Always here or Always anywhere.
Actual result
The daemon extracts this candidate:
verb: git diff install-skills.sh
directory: null
Always here saves this record:
{ "verb": "git diff install-skills.sh", "directory": "/home/alice/repo" }
Always anywhere saves this record:
{ "verb": "git diff install-skills.sh" }
The omitted directory means null.
It represents an approval for all directories.
Expected result
The daemon must extract this candidate:
verb: git diff
directory: /home/alice/repo
The directory is the parent of the relative file operand.
Always here must save this record:
{ "verb": "git diff", "directory": "/home/alice/repo" }
Always anywhere must save this record:
The v2 store does not use the legacy git diff:* text form.
The typed record carries the wildcard through a null directory.
A git diff grant must match another file operand within the selected directory scope.
Root cause
ShellSyntaxTree performs two relevant passes.
- The greedy verb pass accepts lowercase tokens with letters, digits, hyphens, dots, and underscores.
- The argument pass classifies remaining tokens as paths and resolves them.
The first pass consumes install-skills.sh as a verb token.
The second pass never receives that token.
The package path resolver does recognize .sh as a file suffix.
The classification works when syntax stops the verb pass first.
git diff install-skills.sh
verb: git diff install-skills.sh
args: none
git diff -- install-skills.sh
verb: git diff
arg: install-skills.sh, path=true, resolved=/home/alice/repo/install-skills.sh
cat install-skills.sh
verb: cat
arg: install-skills.sh, path=true, resolved=/home/alice/repo/install-skills.sh
Netclaw then trusts Clause.Verb.Tokens as its approval input.
Netclaw removes trailing digit-bearing values, but it does not remove a trailing file-shaped token.
Ownership
The correction belongs in Netclaw.
ShellSyntaxTree documents the greedy verb as a best-effort hint.
Upstream issue Aaronontheweb/ShellSyntaxTree#27 assigns security-grade verb depth to the consumer.
The package also preserves narrow output when syntax cannot distinguish a subcommand from an operand.
Netclaw already owns approval-specific post-processing through TrimTrailingValueTokens.
That layer must also separate a trailing file-shaped token from the approval verb.
It must resolve the file parent against WorkingDirectory for the candidate directory.
An upstream API could expose token classifications before the greedy verb decision.
Such an API could reduce duplicate heuristics, but it is not required for this correction.
Client impact
All interactive clients send only the selected option key.
Therefore, the TUI, Slack, and Discord use the same daemon candidate and persistence path.
This defect is not specific to the TUI.
Verification
Add the case to the shell approval disposition matrix.
Add matcher tests for candidate extraction and stored-grant reuse.
The tests must cover a bare filename with a lowercase name and a file extension.
Risk
This defect creates narrow and ineffective approvals.
It does not create a broader grant than the user selected.
Related: #1331, #1406, and Aaronontheweb/ShellSyntaxTree#27.
Summary
The shell approval matcher treats a bare filename as part of the verb.
The defect affects Netclaw 0.25.2 and the current dev branch.
Reproduction
Assume the command current directory is /home/alice/repo.
Run this command through shell_execute:
Select Always here or Always anywhere.
Actual result
The daemon extracts this candidate:
Always here saves this record:
{ "verb": "git diff install-skills.sh", "directory": "/home/alice/repo" }Always anywhere saves this record:
{ "verb": "git diff install-skills.sh" }The omitted directory means null.
It represents an approval for all directories.
Expected result
The daemon must extract this candidate:
The directory is the parent of the relative file operand.
Always here must save this record:
{ "verb": "git diff", "directory": "/home/alice/repo" }Always anywhere must save this record:
{ "verb": "git diff" }The v2 store does not use the legacy git diff:* text form.
The typed record carries the wildcard through a null directory.
A git diff grant must match another file operand within the selected directory scope.
Root cause
ShellSyntaxTree performs two relevant passes.
The first pass consumes install-skills.sh as a verb token.
The second pass never receives that token.
The package path resolver does recognize .sh as a file suffix.
The classification works when syntax stops the verb pass first.
Netclaw then trusts Clause.Verb.Tokens as its approval input.
Netclaw removes trailing digit-bearing values, but it does not remove a trailing file-shaped token.
Ownership
The correction belongs in Netclaw.
ShellSyntaxTree documents the greedy verb as a best-effort hint.
Upstream issue Aaronontheweb/ShellSyntaxTree#27 assigns security-grade verb depth to the consumer.
The package also preserves narrow output when syntax cannot distinguish a subcommand from an operand.
Netclaw already owns approval-specific post-processing through TrimTrailingValueTokens.
That layer must also separate a trailing file-shaped token from the approval verb.
It must resolve the file parent against WorkingDirectory for the candidate directory.
An upstream API could expose token classifications before the greedy verb decision.
Such an API could reduce duplicate heuristics, but it is not required for this correction.
Client impact
All interactive clients send only the selected option key.
Therefore, the TUI, Slack, and Discord use the same daemon candidate and persistence path.
This defect is not specific to the TUI.
Verification
Add the case to the shell approval disposition matrix.
Add matcher tests for candidate extraction and stored-grant reuse.
The tests must cover a bare filename with a lowercase name and a file extension.
Risk
This defect creates narrow and ineffective approvals.
It does not create a broader grant than the user selected.
Related: #1331, #1406, and Aaronontheweb/ShellSyntaxTree#27.