Skip to content

feat: add 'app request' to check install approval requests status - #646

Merged
mwbrooks merged 9 commits into
slackapi:mainfrom
AmyScript:new-approval-request-status-command
Aug 27, 2026
Merged

feat: add 'app request' to check install approval requests status#646
mwbrooks merged 9 commits into
slackapi:mainfrom
AmyScript:new-approval-request-status-command

Conversation

@AmyScript

@AmyScript AmyScript commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Changelog
Added a slack app request command that checks the status of your most recent request to have an app approved for install.
Fixed the help template so hidden subcommands are no longer listed under their parent command.

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 slack app request command (aliased as requests) 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 --workspace-ids, which the API accepts up to 50 of.

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. Only team IDs are returned by the endpoint, so a team is titled by name when it happens to be the team of the authenticated account:

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

A denied request adds the moment it was reviewed:

🔒 App Install Approval Requests
   App ID:       A01AX7UTK19
   teamname (T0123456789):
     Request ID:   Ar0123456789
     Status:       denied
     Requested:    2026-08-21 15:04:05 -04:00
     Resolved:     2026-08-22 09:30:00 -04:00

A cancelled request names the kind of actor that cancelled it, which is an admin, the system, or you:

🔒 App Install Approval Requests
   App ID:       A01AX7UTK19
   teamname (T0123456789):
     Request ID:   Ar0123456789
     Status:       cancelled
     Requested:    2026-08-21 15:04:05 -04:00
     Resolved:     2026-08-22 09:30:00 -04:00
     Cancelled by: an admin

A pending request where the user can install without approval says so when the account turns out to be able to install the app without approval, because users who can install without approval are still able to create requests:

🔒 App Install Approval Requests
   App ID:       A01AX7UTK19
   teamname (T0123456789):
     Request ID:   Ar0123456789
     Status:       pending
     Requested:    2026-08-21 15:04:05 -04:00
     You can install this app without approval. Please cancel the request.

Design considerations

slack app request was chosen as the command because Slack's vocabulary splits by role rather than by feature: the person who wants the app requests it, and the admin approves it. This command runs as the requester, reading their own request status, so it sits on the request half.

On the requester side, request also shows up in our CLI and UI:

  • The CLI install flow: "Your request to install the app is pending", "Please submit a request to install or update your app" (internal/pkg/apps/install.go), and "Cancel the current request to install this app?" (internal/api/app.go).
  • On the app settings page under Install App, when approval is on, the button is Request to Install, and Slackbot follows up with "Your request to install [APP] on [WORKSPACE] has been sent for review."
  • The member-facing help article has a "Request app approval" section: https://slack.com/help/articles/202035138-Add-apps-to-your-Slack-workspace

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 a project is still required unless an app ID is given, and that the API error for more than 50 workspaces surfaces to the developer.
  • Output is sorted by team and leaves the requests of the caller in the order they arrived.
  • The error codes returned by the endpoint are asserted through their rendered message and remediation rather than the bare code.

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, named ErrAPIFeatureNotEnabled and ErrAPIRestrictedAction so it is clear they mirror API responses rather than errors raised by the CLI.
  • Fixes a pre-existing bug in the help template where hidden subcommands were still listed under a parent command. Without it, slack app --help lists this command despite Hidden. It is a one line change that can be reverted on its own if it ever needs to be.
  • PromptTeamSlackAuth now calls SetSelectedAuth when only one account is authenticated, which the two multi-account paths already did. This sends API calls to the host that account belongs to, and also applies to app link and sandbox, which use the same prompt.
  • 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 97.45223% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.54%. Comparing base (bd2741b) to head (c326c6c).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
cmd/app/request.go 98.48% 1 Missing and 1 partial ⚠️
internal/api/app.go 91.30% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #646      +/-   ##
==========================================
+ Coverage   72.38%   72.54%   +0.16%     
==========================================
  Files         238      239       +1     
  Lines       20072    20229     +157     
==========================================
+ Hits        14529    14676     +147     
- Misses       4275     4276       +1     
- Partials     1268     1277       +9     

☔ 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/request.go
Comment thread cmd/help/help.go
Comment thread internal/slackerror/errors.go Outdated
Comment thread internal/slackerror/errors.go Outdated

@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 Outdated
func NewRequestsCommand(clients *shared.ClientFactory) *cobra.Command {
cmd := &cobra.Command{
Use: "requests [flags]",
Aliases: []string{"approval-requests", "approvals"},

Copy link
Copy Markdown
Contributor 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/request_test.go
Comment thread internal/prompts/team_select_test.go
@AmyScript

AmyScript commented Aug 25, 2026

Copy link
Copy Markdown
Contributor 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 Outdated
// 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hey @mwbrooks, thanks for the thought you put into this, and for surfacing that there's been a request for a cancel feature.

I should have explained the design considerations for using app requests in the PR description. My thinking is that Slack's vocabulary splits by role rather than by feature: the person who wants the app requests it, and the admin approves it. This command runs as the requester, reading their own request status, so it sits on the request half.

That split shows up in the sources you linked, too. Both are written for the approver, and even there, the object is a request: the admin listing method is admin.apps.requests.list, the event is app_requested, the companion help article is "Manage app requests for your workspace".

On the requester side, request also shows up in our CLI and UI:

  • The CLI install flow: "Your request to install the app is pending", "Please submit a request to install or update your app" (internal/pkg/apps/install.go), and "Cancel the current request to install this app?" (internal/api/app.go).
  • On the app settings page under Install App, when approval is on, the button is Request to Install, and Slackbot follows up with "Your request to install [APP] on [WORKSPACE] has been sent for review."
  • The member-facing help article has a "Request app approval" section: https://slack.com/help/articles/202035138-Add-apps-to-your-Slack-workspace

Good point about leaving room for subcommands. The requester side verbs are create, cancel, and list, and those read naturally as app request cancel, our own enum comment describes that action as "the requester withdrawing their own request". Whereas app approval cancel sounds like revoking an approval someone granted. That's also why I left approval out of the aliases. If app request claims that name now, we can't give it to an admin side app approval later if we need it.

You're right that we should use the singular, so I've renamed it to app request with requests as the only alias. The command is hidden behind the app-approval-status experiment right now, so if you still feel strongly about it, I am happy to switch it if you'd rather go with approval. Let me know what you think!

Comment thread cmd/app/requests.go Outdated
Comment thread internal/prompts/team_select.go
Comment thread cmd/app/requests.go Outdated
Comment thread cmd/app/app.go Outdated
Comment thread cmd/app/requests.go Outdated
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 Outdated
Comment thread cmd/app/requests.go Outdated
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 Outdated
// 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 Outdated
Rename the command to the singular "app request" with "requests" as the
only alias, matching the CLI convention of a singular canonical name.

Rename the API error codes to ErrAPIFeatureNotEnabled and
ErrAPIRestrictedAction so it is clear they mirror responses of the API
rather than errors raised by the CLI.

Sort a copy of the requests while formatting so the slice of the caller
keeps its order, and title the section "App Install Approval Requests"
to spell out what is being listed.

Co-authored-by: Cursor <cursoragent@cursor.com>
@AmyScript

Copy link
Copy Markdown
Contributor Author

🙌🏻 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!

@mwbrooks Thanks for your review on my first PR working with this codebase, lots of great suggestions. The code has been updated for another review. I've left the naming of the new command for now. I'm leaving on PTO next week, so that's why I was hoping to launch this by the end of the week, but if we think there are still discussions that need to happen, I'm happy to leave the experiment flag on and merge it for now and pick it back up when I'm back from PTO.

I'll create a branch off the repo once i get a ✅ from you so we can merge the branch instead.

@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 Thanks for fixes and changes. 🙇🏻

I understand your reasoning for app requests although I still feel it isn't a self-documenting command for approval requests. Regardless, naming is hard and I don't want to block your PR while we try to decide on a better name.

✏️ Last change request before we can merge - please remove the experiment gate. If you want this feature to be available to customers next week, it can't be behind an experiment.

Comment thread cmd/app/request.go
Comment thread internal/slackerror/errors.go
Comment thread cmd/app/requests.go Outdated
The app-approval-status experiment gated the command while the API and
output were settled. Both are now agreed on, so the gate and the hidden
flag come off and the command joins the app subcommands in help.

Co-authored-by: Cursor <cursoragent@cursor.com>
@AmyScript

Copy link
Copy Markdown
Contributor Author

Moved to #650, which is the same code on a branch in this repo rather than my fork so that CI can reach the repository secrets.

The only change since the last review here is removing the app-approval-status experiment gate and the Hidden flag, now that we have the go-ahead to ship the command. Everything else is identical.

Closing this in favor of #650 — thanks for the reviews, and sorry for the thread shuffle.

@AmyScript AmyScript closed this Aug 27, 2026
@AmyScript AmyScript reopened this Aug 27, 2026
@AmyScript

Copy link
Copy Markdown
Contributor Author

Exact branch with passing tests: #650

@AmyScript AmyScript removed the experiment Experimental feature accessed behind the --experiment flag or toggle label Aug 27, 2026
@mwbrooks mwbrooks changed the title feat: check requests to install an app feat: add 'app request' to check install approval requests status Aug 27, 2026

@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 @AmyScript, adding an approval to the PR!

🙇🏻 Appreciate all the back and forth. After learning about your tight release schedule, dropping the experiment will allow us to release it anytime.

🧪 Thanks for confirming the our E2E tests pass with PR #650. In the future you can branch off the main repo to avoid that.

@mwbrooks
mwbrooks merged commit 2f62b9c into slackapi:main Aug 27, 2026
12 checks passed
@mwbrooks mwbrooks added semver:minor Use on pull requests to describe the release version increment and removed semver:patch Use on pull requests to describe the release version increment labels Aug 27, 2026
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 semver:minor 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