Skip to content

feat: check requests to install an app - #646

Open
AmyScript wants to merge 7 commits into
slackapi:mainfrom
AmyScript:new-approval-request-status-command
Open

feat: check requests to install an app#646
AmyScript wants to merge 7 commits into
slackapi:mainfrom
AmyScript:new-approval-request-status-command

Conversation

@AmyScript

@AmyScript AmyScript commented Aug 21, 2026

Copy link
Copy Markdown

Changelog

Added the app-approval-status experiment with a slack app requests command that checks the status of your most recent request to have an app approved for install.

Summary

Installing an app on a team with admin approval required creates an approval request, but there was no way to check what happened to it from the CLI. This adds a hidden slack app requests command, gated behind the new app-approval-status experiment, that reports the most recent request for the selected app on each team in the token's scope.

Requests are searched on the team of the authenticated account. An account of a workspace belonging to an organization also searches that organization, while an account of an organization searches the organization alone. Other workspaces of an organization can be searched with --team-ids, up to the 50 the API accepts.

Apps saved to a project are chosen with the usual prompt. Since that prompt only offers apps recorded in the project, an app can also be named by ID with --app, which skips both the project requirement and the project app list and gathers a token from the authenticated accounts instead.

Output includes the request ID, status, and timestamps, plus the actor that cancelled a request and a hint when the account can install the app without approval:

🔒 App Requests
   App ID: A01AX7UTK19
   T0123456789:
     Request ID:   Ar0123456789
     Status:       pending
     Requested:    2026-08-21 15:04:05 -04:00

Preview

No recording yet — the command is unreleased and hidden, and the output above is taken from the unit tests rather than a live run.

Testing

  • Unit tests cover the API client against a mocked server and the output formatting for every status and cancellation actor.
  • Both app selection paths are covered: the project prompt and the --app app ID path, including a failed team selection and a team without a token.
  • Verified the experiment gate rejects the command without --experiment app-approval-status, that --team-ids rejects more than 50 teams, and that a project is still required unless an app ID is given.

Notes

  • The command is registered unconditionally and marked Hidden, matching how manifest sync gates itself; the experiment check lives in PreRunE.
  • Adds the feature_not_enabled and restricted_action error codes returned by the endpoint.
  • Fixes a pre-existing bug in the help template where hidden subcommands were still listed under a parent command.
  • app_id is required by the endpoint, so requests are reported one app at a time rather than for every app the account owns.

Requirements

@AmyScript
AmyScript requested a review from srtaalej August 21, 2026 21:41
@codecov

codecov Bot commented Aug 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.71795% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.53%. Comparing base (bd2741b) to head (621d13e).

Files with missing lines Patch % Lines
internal/api/app.go 91.30% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #646      +/-   ##
==========================================
+ Coverage   72.38%   72.53%   +0.15%     
==========================================
  Files         238      239       +1     
  Lines       20072    20228     +156     
==========================================
+ Hits        14529    14673     +144     
- Misses       4275     4279       +4     
- Partials     1268     1276       +8     

☔ View full report in Codecov by Harness.
📢 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.

AmyScript and others added 2 commits August 24, 2026 11:00
Add a hidden `slack app requests` command behind the app-approval-status experiment that reports the most recent install approval request for the selected app on each team in the token's scope.

Co-authored-by: Cursor <cursoragent@cursor.com>
Exercise the interrupted app selection and missing app ID branches of the command, plus the unknown timestamp, status, and cancellation actor fallbacks of the output.

Co-authored-by: Cursor <cursoragent@cursor.com>
@AmyScript
AmyScript force-pushed the new-approval-request-status-command branch from fc808a6 to 1e4be76 Compare August 24, 2026 15:01
AmyScript and others added 3 commits August 24, 2026 13:39
The app select prompt only offers apps saved to a project, so apps created elsewhere could not be checked. An app ID provided with the --app flag now skips both the project requirement and the project app list, gathering a token from the authenticated accounts instead.

Co-authored-by: Cursor <cursoragent@cursor.com>
@AmyScript
AmyScript marked this pull request as ready for review August 24, 2026 19:08
@AmyScript
AmyScript requested review from a team as code owners August 24, 2026 19:08
@zimeg zimeg added enhancement M-T: A feature request for new functionality experiment Experimental feature accessed behind the --experiment flag or toggle semver:patch Use on pull requests to describe the release version increment labels Aug 25, 2026
@zimeg zimeg added this to the Next Release milestone Aug 25, 2026

@zimeg zimeg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🏁 @AmyScript Thanks for sending this in! It's LGTM per usual and I leave a few comments that might be nice before merge but some are rambles... 🗣️

Overall I lean toward returning API responses or errors instead of expecting certain values and some flag options and error outputs. Nothing blocking! These are exciting experiment to bring to release 🤖⚡

Comment thread cmd/app/requests.go Outdated
Comment thread cmd/app/requests.go Outdated
Comment thread cmd/app/requests.go Outdated
Comment thread cmd/app/requests.go Outdated
Comment thread cmd/app/requests.go
Comment thread cmd/help/help.go
Comment on lines +792 to +797
ErrFeatureNotEnabled: {
Code: ErrFeatureNotEnabled,
Message: "This feature is not enabled for the team",
Remediation: "Reach out to an admin for additional information",
},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
ErrFeatureNotEnabled: {
Code: ErrFeatureNotEnabled,
Message: "This feature is not enabled for the team",
Remediation: "Reach out to an admin for additional information",
},

🪓 note: I don't think this is used in calling code and might be nice to keep out? Would API responses be alright to match this?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It is used, just not through explicit calling code. slackerror.NewAPIError calls getErrorIfKnown, which looks up ErrorCodeMap by whatever code the API returned, so this entry is what turns feature_not_enabled into a message and remediation. Without it the user sees the bare code. I added an API test case asserting the rendered message so the wiring is visible in the suite.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be good to update the name to ErrAPIFeatureNotEnabled so that it's self-documenting that this is an API error and not an internal Slack CLI error. Otherwise, future maintainers may remove it under the same assumption as @zimeg.

Comment on lines +1393 to +1398
ErrRestrictedAction: {
Code: ErrRestrictedAction,
Message: "The requested action is not allowed for a specified team",
Remediation: "Check that each team belongs to the organization of the authenticated account",
},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
ErrRestrictedAction: {
Code: ErrRestrictedAction,
Message: "The requested action is not allowed for a specified team",
Remediation: "Check that each team belongs to the organization of the authenticated account",
},

🌲 ramble: I fear checking for errors perhaps unreferenced similar but I'm wondering if this is used in unit tests or when handling the API response? If it's alright to remove I might suggest but all so is rambles!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Same mechanism as above. Test_Client_ListAppApprovalRequests has an "errors when a team is outside of the organization" case returning restricted_action. I strengthened it to assert the mapped message and remediation rather than just the code, so removing the entry would now fail the test rather than silently degrade the output.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same suggestion as above to rename it ErrAPIRestrictedAction so that it's self-documenting that it's an API error.

@mwbrooks mwbrooks left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@AmyScript Please don't merge this yet - I'd like a moment to review it. Just dropping the change request, since @zimeg already approved it which enables merging.

Comment thread cmd/app/requests.go
func NewRequestsCommand(clients *shared.ClientFactory) *cobra.Command {
cmd := &cobra.Command{
Use: "requests [flags]",
Aliases: []string{"approval-requests", "approvals"},

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@mwbrooks here are the aliases I was talking about.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

suggestion: Assuming we go with app approval, alias approvals and remove the others. It's hard to remove an alias once we add it, but it's easy to add one in the future.

Suggested change
Aliases: []string{"approval-requests", "approvals"},
Aliases: []string{"approvals"},

@zimeg zimeg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@AmyScript Super appreciate the fast changes! I'll toss another approval on but defer to @mwbrooks for ongoing review. Nothing is blocking experimental releases for me!

🧪 I do notice the E2E tests aren't running and I think that's an intentional setting to avoid passing secrets to forked branches... It catches us often and I'm unsure a workaround remains but I share this for possible updates:

🔗 https://github.com/slackapi/slack-cli/blob/main/.github/MAINTAINERS_GUIDE.md#fork

Comment thread cmd/help/help.go
Comment thread cmd/app/requests.go Outdated
Comment thread cmd/app/requests_test.go
},
Expected: []string{"Cancelled by: workflow"},
},
"requests are sorted by the team ID": {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🧮 praise: Thanks for bringing consistent outputs to this command!

"github.com/stretchr/testify/require"
)

func TestPromptTeamSlackAuth(t *testing.T) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🦠 praise: Thanks for adding coverage to this prompt!

🔬 ramble: I'm hoping we can improve the mock longterm to avoid stubbing these selections within each command and this is so helpful in building confidence toward this I think.

@AmyScript

AmyScript commented Aug 25, 2026

Copy link
Copy Markdown
Author

@AmyScript Super appreciate the fast changes! I'll toss another approval on but defer to @mwbrooks for ongoing review. Nothing is blocking experimental releases for me!

🧪 I do notice the E2E tests aren't running and I think that's an intentional setting to avoid passing secrets to forked branches... It catches us often and I'm unsure a workaround remains but I share this for possible updates:

🔗 https://github.com/slackapi/slack-cli/blob/main/.github/MAINTAINERS_GUIDE.md#fork

Should I have just branched off main? I read about the fork so I forked 😆. Is there a way for me to run the e2e tests locally with a command?

@mwbrooks mwbrooks left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🙌🏻 Thanks for the PR @AmyScript! Admin approval management has been a painfully missed feature in the CLI, so I'm happy to see you giving it some TLC.

🙇🏻 Some praise goes to you on code quality. You rocked our latest conventions, patterns, and test coverage goals. Thanks for that!

💡 Small tip for next time - please create a branch on this repo instead of forking. External contributors fork, but as a maintainer you can work on a branch allowing our CI/CD to safely run E2E tests. Before merging, we should push this to a branch on the origin to ensure the E2E tests pass alright.

🧠 My main ask before approving this PR is to consider app approval (alias: approvals) instead of app requests. I think it reads better from the user/developer perspective, aligns with our documentation, and aligns with our API. It also opens space for 3rd-tier of commands such as app approval cancel or app approval list (alias app approval - what you're landing here).

🧪 I hear you're aiming to land this into production end-of-week or early next week. I'd suggestion you delete the experiment entirely, because we won't be doing a release between now-and-then. All production features should not be behind an experiment.

🚦 When you remove the experiment, please switch this to a semver:minor.

📝 The rest of my suggestions are minor. Nice to haves but not blockers if you are short on time.

Once we settle on the name, happy to hit ✅ approve!

Comment thread cmd/app/requests.go
// NewRequestsCommand returns a new Cobra command
func NewRequestsCommand(clients *shared.ClientFactory) *cobra.Command {
cmd := &cobra.Command{
Use: "requests [flags]",

@mwbrooks mwbrooks Aug 25, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

suggestion: After some thought, I'd like to suggest renaming this to approval (singular)

The reason is that app approval matches the API (apps.approvals.requests.list) and leaves room for slack app approval [create|cancel|list] later.

We've had requests for a cancel feature, so this opens the namespace for it.

The bare approval also matches the bare app → app list, trigger → trigger list, auth → auth list, and manifest → manifest list convention. So, if we expand the command with create | cancel | list subcommands, we've made decisions that allow for a smooth forward rollout.

The approvals command also matches the terminology used in our documentation, where as requests is more from the admin-perspective:

We should alias the plural approvals. The CLI usually has the canonical command as singular while aliasing the plural. Long ago, we mixed-and-matched singular/plural depending on the command and it was confusing for developers. So, we try to use singular when possible.

Comment thread cmd/app/requests.go
},
}

cmd.Flags().StringSliceVar(&requestsFlags.workspaceIDs, "workspace-ids", nil, "also check these workspaces of an organization,\nwith a maximum of 50 workspaces")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

thought: Is the 50 workspace limit enforced by the API? A brief look and it appears the API returns invalid_arguments with no remediation. If so, we may want to validate the length here and include it in the error message since it's easy to overlook the help documentation.

}

if len(allAuths) == 1 {
clients.Auth().SetSelectedAuth(ctx, allAuths[0], clients.Config, clients.Os)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

note: Right place for this, but it also changes app link and sandbox, which now get the selected auth set on the single-account path. Worth confirming neither relies on it being unset.

Comment thread cmd/app/requests.go

// FormatRequestsSuccess formats the install request of each team for an app
func FormatRequestsSuccess(appID string, requests []api.AppsApprovalsRequest) (secondaryText []string) {
sort.Slice(requests, func(i, j int) bool {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

suggestion: sort.Slice reorders the caller's original slice. Harmless today, but this function mutating its input will be surprise later if used elsewhere and can introduce unexpected bugs. I'd recommend sorting a copy of requests (requestsSorted).

Comment thread cmd/app/app.go
cmd.AddCommand(NewDeleteCommand(clients))
cmd.AddCommand(NewLinkCommand(clients))
cmd.AddCommand(NewListCommand(clients))
cmd.AddCommand(NewRequestsCommand(clients))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

praise: Appreciate the attention to detail on naming conventions and alphabetical order. 🙇🏻

Comment thread cmd/app/requests.go
func NewRequestsCommand(clients *shared.ClientFactory) *cobra.Command {
cmd := &cobra.Command{
Use: "requests [flags]",
Aliases: []string{"approval-requests", "approvals"},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

suggestion: Assuming we go with app approval, alias approvals and remove the others. It's hard to remove an alias once we add it, but it's easy to add one in the future.

Suggested change
Aliases: []string{"approval-requests", "approvals"},
Aliases: []string{"approvals"},

Comment thread cmd/app/requests.go
cmd := &cobra.Command{
Use: "requests [flags]",
Aliases: []string{"approval-requests", "approvals"},
Short: "Check requests to install the app",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

suggestion: Most developers aren't familiar with AAA, so I think the extra word can go along way and aligns with our documentation.

Suggested change
Short: "Check requests to install the app",
Short: "Check approval requests to install the app",

Comment thread cmd/app/requests.go
Comment on lines +78 to +80
{Command: "app requests", Meaning: "Check requests to install an app"},
{Command: "app requests --app A0123456789", Meaning: "Check requests for an app outside a project"},
{Command: "app requests --workspace-ids T0123456789,T9876543210", Meaning: "Check requests on certain workspaces of an organization"},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

suggestion: Assuming we go with app approval:

Suggested change
{Command: "app requests", Meaning: "Check requests to install an app"},
{Command: "app requests --app A0123456789", Meaning: "Check requests for an app outside a project"},
{Command: "app requests --workspace-ids T0123456789,T9876543210", Meaning: "Check requests on certain workspaces of an organization"},
{Command: "app approval", Meaning: "Check requests to install an app"},
{Command: "app approval --app A0123456789", Meaning: "Check requests for an app outside a project"},
{Command: "app approval --workspace-ids T0123456789,T9876543210", Meaning: "Check requests on certain workspaces of an organization"},

Comment thread cmd/app/requests.go
// runRequestsCommand will execute the requests command
func runRequestsCommand(cmd *cobra.Command, clients *shared.ClientFactory) error {
ctx := cmd.Context()
span, ctx := opentracing.StartSpanFromContext(ctx, "cmd.app.requests")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

suggestion: If we accept app approval then we should adjust all of our tracing.

Suggested change
span, ctx := opentracing.StartSpanFromContext(ctx, "cmd.app.requests")
span, ctx := opentracing.StartSpanFromContext(ctx, "cmd.app.approval")

Comment thread cmd/app/requests.go

clients.IO.PrintInfo(ctx, false, "\n%s", style.Sectionf(style.TextSection{
Emoji: "lock",
Text: "App Requests",

@mwbrooks mwbrooks Aug 25, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

suggestion: Adding more clarity for developers who aren't familiar with AAA.

Suggested change
Text: "App Requests",
Text: "App Approval Requests",

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement M-T: A feature request for new functionality experiment Experimental feature accessed behind the --experiment flag or toggle semver:patch Use on pull requests to describe the release version increment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants