Skip to content

fix(shortcuts): reject unsupported shortcut commands#896

Open
liangshuo-1 wants to merge 3 commits into
mainfrom
fix/unknown-shortcut-error
Open

fix(shortcuts): reject unsupported shortcut commands#896
liangshuo-1 wants to merge 3 commits into
mainfrom
fix/unknown-shortcut-error

Conversation

@liangshuo-1
Copy link
Copy Markdown
Collaborator

@liangshuo-1 liangshuo-1 commented May 14, 2026

Summary

  • Add a root-level command preflight so unsupported top-level commands and unsupported +shortcut tokens fail before Cobra falls back to parent help.
  • Return structured validation errors with exit code 2, actionable hints, and machine-readable detail:
    • unknown_command for root-level command typos.
    • unknown_shortcut for service shortcut typos.
  • Preserve normal root/service help and existing Cobra errors for valid commands, flags, and known shortcuts.

Root cause

Cobra can match a parent command when a token remains unmatched. For service shortcuts this meant lark-cli drive +nonexistent-thing matched the drive parent and returned normal help with exit code 0. The same class of issue can hide root-level typos behind generic Cobra behavior instead of a stable agent-readable error contract.

Change

The new validateCommandInvocation preflight handles only the two agent-hostile cases before normal Cobra execution:

  1. The first non-flag root token is not any registered top-level command or alias -> structured unknown_command.
  2. A service command is matched, the next token starts with +, and that shortcut is not in the service shortcut catalog -> structured unknown_shortcut.

The preflight intentionally does not parse dynamic API resource/method paths, known shortcut flags, or other Cobra parse errors. Those stay on the existing command-specific paths.

Verification

  • go test ./cmd -run 'TestIntegration_(UnknownTopLevelCommand|RootHelpStillSucceeds|NonServiceTopLevelCommandStillWorks|UnknownShortcut|EmptyShortcut|ServiceHelpStillSucceeds|KnownShortcutUnknownFlagKeepsExistingBehavior)' -count=1
  • go test ./cmd ./shortcuts ./shortcuts/common ./shortcuts/drive -count=1
  • go test $(go list ./... | grep -v '/tests/cli_e2e')
  • Manual binary checks:
    • lark-cli nonexistent-service -> exit 2 structured unknown_command, empty stdout, no Cobra "Did you mean" text.
    • lark-cli nonexistent-service --help -> exit 2 structured unknown_command, empty stdout.
    • lark-cli drive +nonexistent-thing -> exit 2 structured unknown_shortcut, empty stdout.
    • lark-cli drive +nonexistent-thing --help -> exit 2 structured unknown_shortcut, empty stdout.
    • lark-cli drive --help -> existing parent help, exit 0.
    • lark-cli drive +search --bogus -> existing unknown-flag behavior preserved.

Notes

go test ./... was also attempted before this revision, but existing live e2e packages failed due environment/scopes and stale e2e expectations unrelated to this change. Non-e2e packages are green.

Summary by CodeRabbit

  • Bug Fixes

    • Unsupported shortcuts and unknown top-level commands now return structured, user-friendly error messages with bounded hints listing available shortcuts/commands and guidance to use the service help, instead of silently showing parent help.
  • Tests

    • Added integration tests covering unknown shortcuts, empty shortcut, unknown commands, help invocations, and invalid-flag scenarios to ensure consistent validation and error output.

Review Change Stack

@github-actions github-actions Bot added the size/L Large or sensitive change across domains or core paths label May 14, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 14, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4e80eee5-d089-40cb-aea7-6b0cda64deef

📥 Commits

Reviewing files that changed from the base of the PR and between da65f77 and df71c4d.

📒 Files selected for processing (1)
  • cmd/command_preflight.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/command_preflight.go

📝 Walkthrough

Walkthrough

Adds preflight validation to detect unsupported +shortcut arguments and unknown top-level commands before executing the root command, returning structured error envelopes; integrates validation into root execution and adds integration tests and a changelog entry.

Changes

Command Invocation Validation

Layer / File(s) Summary
Preflight validation implementation
cmd/command_preflight.go
Validates CLI arguments by scanning for the first root token, traversing Cobra resolution, detecting unsupported +-shortcuts and unknown top-level commands, and building structured ExitError envelopes with bounded shortcut/command hint lists.
Root execution integration
cmd/root.go
Execute calls validateCommandInvocation for non-completion invocations before setupNotices(); validation errors short-circuit via handleRootError.
Integration test coverage
cmd/root_integration_test.go
Test helper applies preflight validation; tests assert structured ErrorEnvelope for unknown shortcuts (plain, with --help, empty), unknown commands, and verify help and existing Cobra behaviors remain unchanged for valid shortcuts/commands.
Release notes
CHANGELOG.md
Adds v1.0.31 Bug Fixes entry documenting structured error handling for unsupported shortcuts instead of silently showing parent help.

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant validateCommandInvocation
  participant handleRootError
  participant setupNotices
  participant CobraExecute
  CLI->>validateCommandInvocation: validateCommandInvocation(rootCmd, args)
  validateCommandInvocation-->>handleRootError: return unknown_shortcut / unknown_command (ExitError)
  validateCommandInvocation->>setupNotices: nil (validation OK)
  setupNotices->>CobraExecute: rootCmd.Execute()
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

size/M

Suggested reviewers

  • chanthuang

🐰 A shortcut validator hops in, keen and spry,
Finds lonely + marks and gives them a try,
Errors now structured with hints that are neat,
Guiding small paws to the help they should meet—
Hooray for clear output and tidy CLI feet! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.04% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding validation to reject unsupported shortcut commands with structured errors before Cobra execution.
Description check ✅ Passed The description comprehensively covers all required template sections: Summary explains the motivation and scope clearly, Changes lists main modifications, Test Plan documents verification approach, and Related Issues is addressed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/unknown-shortcut-error

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@liangshuo-1 liangshuo-1 force-pushed the fix/unknown-shortcut-error branch from d301499 to 5c0e2b8 Compare May 14, 2026 14:42
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 14, 2026

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@df71c4d8d91f7374459e633bf4a43fee384d875c

🧩 Skill update

npx skills add larksuite/cli#fix/unknown-shortcut-error -y -g

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
cmd/root_integration_test.go (1)

524-535: ⚡ Quick win

Assert the machine-readable detail payload too.

These tests only lock down type, message, and hint text. The new contract also includes shortcut, service, and available_shortcuts; if those fields disappear or change shape, this suite still passes even though agent-facing behavior regressed.

Suggested assertion additions
 	env := parseEnvelope(t, stderr)
 	if env.Error == nil {
 		t.Fatal("expected error envelope detail")
 	}
 	if env.Error.Type != "unknown_shortcut" {
 		t.Fatalf("error type = %q, want unknown_shortcut", env.Error.Type)
 	}
+	if env.Error.Detail["shortcut"] != "+nonexistent-thing" {
+		t.Fatalf("detail.shortcut = %#v, want +nonexistent-thing", env.Error.Detail["shortcut"])
+	}
+	if env.Error.Detail["service"] != "drive" {
+		t.Fatalf("detail.service = %#v, want drive", env.Error.Detail["service"])
+	}
+	if got, ok := env.Error.Detail["available_shortcuts"].([]interface{}); !ok || len(got) == 0 {
+		t.Fatalf("detail.available_shortcuts = %#v, want non-empty list", env.Error.Detail["available_shortcuts"])
+	}
 	if !strings.Contains(env.Error.Message, `shortcut "+nonexistent-thing" is not supported for "lark-cli drive"`) {
 		t.Fatalf("message missing unsupported shortcut context: %q", env.Error.Message)
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/root_integration_test.go` around lines 524 - 535, The test currently
verifies env.Error.Type, Message, and Hint but not the machine-readable detail
fields; update the integration test that calls parseEnvelope/t and checks
env.Error to also assert the presence and expected values/shapes of
env.Error.Shortcut (e.g. equals "+nonexistent-thing" or non-empty),
env.Error.Service (e.g. "drive"), and env.Error.AvailableShortcuts (contains
"+upload" and is a non-empty list), and ensure available_shortcuts is the
expected slice/map shape rather than nil so regressions in those fields fail the
test; reference the parseEnvelope result via env.Error.Shortcut,
env.Error.Service, and env.Error.AvailableShortcuts when adding these
assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/shortcut_preflight.go`:
- Around line 26-27: availableShortcutCommands currently reads from the global
shortcut registry and so can return commands that have been pruned from the
mounted Cobra tree; change it to traverse the mounted Cobra tree starting from
the current command/root using cmd.Commands() (or cmd.Root().Commands()) and
collect shortcuts only from commands that are present and available (skip
commands where cmd.Hidden is true or !cmd.IsAvailableCommand() or
cmd.IsHelpCommand()), then return that set for available_shortcuts and hints;
update all call sites (the uses around available :=
availableShortcutCommands(cmd.Name()) and the block at lines 47-60) to pass the
active cmd/root or to call the new tree-based helper so suggestions respect
runtime pruning.

---

Nitpick comments:
In `@cmd/root_integration_test.go`:
- Around line 524-535: The test currently verifies env.Error.Type, Message, and
Hint but not the machine-readable detail fields; update the integration test
that calls parseEnvelope/t and checks env.Error to also assert the presence and
expected values/shapes of env.Error.Shortcut (e.g. equals "+nonexistent-thing"
or non-empty), env.Error.Service (e.g. "drive"), and
env.Error.AvailableShortcuts (contains "+upload" and is a non-empty list), and
ensure available_shortcuts is the expected slice/map shape rather than nil so
regressions in those fields fail the test; reference the parseEnvelope result
via env.Error.Shortcut, env.Error.Service, and env.Error.AvailableShortcuts when
adding these assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 428355ff-fbec-43d7-a3d0-af2618bb0733

📥 Commits

Reviewing files that changed from the base of the PR and between d301499 and 5c0e2b8.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • cmd/root.go
  • cmd/root_integration_test.go
  • cmd/shortcut_preflight.go
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/root.go

Comment thread cmd/shortcut_preflight.go Outdated
Comment on lines +26 to +27
available := availableShortcutCommands(cmd.Name())
if len(available) == 0 {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Build available_shortcuts from the mounted Cobra tree, not the global shortcut registry.

availableShortcutCommands(cmd.Name()) ignores runtime pruning, so unknown_shortcut can suggest commands that the current tree has already hidden. This file’s strict-mode coverage already proves im --help can hide +messages-search; with the current helper, im +bogus would still advertise it in both the hint and available_shortcuts detail.

Suggested fix
-	available := availableShortcutCommands(cmd.Name())
+	available := availableShortcutCommands(cmd)
 	if len(available) == 0 {
 		return nil
 	}
@@
-func availableShortcutCommands(service string) []string {
+func availableShortcutCommands(cmd *cobra.Command) []string {
 	seen := map[string]struct{}{}
 	out := make([]string, 0)
-	for _, shortcut := range shortcuts.AllShortcuts() {
-		if shortcut.Service != service || shortcut.Hidden {
+	for _, child := range cmd.Commands() {
+		name := child.Name()
+		if child.Hidden || !strings.HasPrefix(name, "+") {
 			continue
 		}
-		if _, ok := seen[shortcut.Command]; ok {
+		if _, ok := seen[name]; ok {
 			continue
 		}
-		seen[shortcut.Command] = struct{}{}
-		out = append(out, shortcut.Command)
+		seen[name] = struct{}{}
+		out = append(out, name)
 	}
 	return out
 }

Also applies to: 47-60

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/shortcut_preflight.go` around lines 26 - 27, availableShortcutCommands
currently reads from the global shortcut registry and so can return commands
that have been pruned from the mounted Cobra tree; change it to traverse the
mounted Cobra tree starting from the current command/root using cmd.Commands()
(or cmd.Root().Commands()) and collect shortcuts only from commands that are
present and available (skip commands where cmd.Hidden is true or
!cmd.IsAvailableCommand() or cmd.IsHelpCommand()), then return that set for
available_shortcuts and hints; update all call sites (the uses around available
:= availableShortcutCommands(cmd.Name()) and the block at lines 47-60) to pass
the active cmd/root or to call the new tree-based helper so suggestions respect
runtime pruning.

Unsupported root commands and +shortcut tokens could fall through to parent Cobra commands, producing normal help output or generic Cobra text instead of a stable agent-readable error. That made command typos look successful to agents.

Add root command preflight validation for two narrow cases: an unmatched top-level command token returns structured unknown_command, and an unmatched service +shortcut token returns structured unknown_shortcut. Normal help, known top-level commands, known shortcut flag errors, and dynamic API resource/method paths stay on the existing Cobra paths.

Expose available_commands and available_services for root typos, and available_shortcuts for shortcut typos, with truncated human hints and machine-readable detail.

Cover unknown command, unknown command with --help, unknown command followed by +shortcut, unsupported shortcut, unsupported shortcut with --help, empty shortcut token, root/service help, non-service top-level commands, and known-shortcut unknown flag handling in integration tests.

Change-Id: I045a4e7fbcb56aa57d5314e812d602a543dc5575
@liangshuo-1 liangshuo-1 force-pushed the fix/unknown-shortcut-error branch from 5c0e2b8 to da65f77 Compare May 14, 2026 14:49
@codecov
Copy link
Copy Markdown

codecov Bot commented May 14, 2026

Codecov Report

❌ Patch coverage is 62.87879% with 49 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.90%. Comparing base (a93fb2d) to head (df71c4d).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
cmd/command_preflight.go 63.84% 34 Missing and 13 partials ⚠️
cmd/root.go 0.00% 2 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main     #896    +/-   ##
========================================
  Coverage   65.90%   65.90%            
========================================
  Files         520      521     +1     
  Lines       49274    49409   +135     
========================================
+ Hits        32474    32563    +89     
- Misses      14026    14059    +33     
- Partials     2774     2787    +13     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
cmd/command_preflight.go (1)

58-106: 💤 Low value

Consider adding inline comments for flag parsing branches.

The flag parsing logic correctly handles long flags (--name=value), short flags (-h value), and edge cases (--, --help), but the control flow through multiple branches could benefit from brief inline comments explaining each case (e.g., // Skip double-dash terminator and process next token at line 65).

This would improve maintainability when revisiting the parser logic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/command_preflight.go` around lines 58 - 106, In function
firstRootCommandToken add brief inline comments to clarify each parsing branch:
annotate the handling of the double-dash terminator ("--") and that it returns
the next token, the explicit help flags check ("--help", "-h"), the long-flag
branch (strings.HasPrefix(arg, "--")) including note about handling inline
values ("--name=value") and advancing i when the flag consumes a value, the
short-flag branch (strings.HasPrefix(arg, "-") && arg != "-") including the
shorthand lookup and value-consumption advance, and the default case that
returns the first non-flag token; reference the symbols root.PersistentFlags(),
flag := flags.Lookup(...), flags.ShorthandLookup(...), and
flagConsumesValue(flag) when placing comments.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cmd/command_preflight.go`:
- Around line 58-106: In function firstRootCommandToken add brief inline
comments to clarify each parsing branch: annotate the handling of the
double-dash terminator ("--") and that it returns the next token, the explicit
help flags check ("--help", "-h"), the long-flag branch (strings.HasPrefix(arg,
"--")) including note about handling inline values ("--name=value") and
advancing i when the flag consumes a value, the short-flag branch
(strings.HasPrefix(arg, "-") && arg != "-") including the shorthand lookup and
value-consumption advance, and the default case that returns the first non-flag
token; reference the symbols root.PersistentFlags(), flag := flags.Lookup(...),
flags.ShorthandLookup(...), and flagConsumesValue(flag) when placing comments.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 288ecf2e-1e30-4a0d-bba7-7e79fde2d070

📥 Commits

Reviewing files that changed from the base of the PR and between 5c0e2b8 and da65f77.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • cmd/command_preflight.go
  • cmd/root.go
  • cmd/root_integration_test.go
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md

liangshuo-1 and others added 2 commits May 14, 2026 23:06
Split the err branch and document intentional deferral to Execute with
//nolint:nilerr so golangci-lint no longer flags returning nil after Traverse errors.

Change-Id: Id671fc7f4fda15e74a45f86d2723c958401860a9
Co-authored-by: Cursor <cursoragent@cursor.com>
The command preflight intentionally leaves ordinary Cobra parse errors on the normal Execute path. Discard the Traverse error directly instead of returning nil from a non-nil error branch, avoiding nilerr without a nolint escape hatch.

Change-Id: I482033a5de635cc0a93621ef046ca94680142fed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L Large or sensitive change across domains or core paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant