feat(zulip_bot): close-pr command#155
Merged
bryangingechen merged 24 commits intomasterfrom Apr 17, 2026
Merged
Conversation
Covers command flow, permission check (PR author + collaborator write/admin via GitHub App), token service, confirmation form, post-close actions (sync, DM, repo log thread), and settings (ZULIP_CLOSE_PR_MUTATIONS_ENABLED, ZULIP_REPO_LOG). Notes required queueboard-assignment app permission upgrades: Pull requests: Read and write, Members: Read (org). Custom close-message follow-up planned as commit 3. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rmation form
Adds a `close-pr` Zulip bot command that:
- Checks the invoker's linked GitHub account has permission to close
the target PR (PR author or write/admin collaborator), using a
GitHub App token for the permission check
- Issues a short-lived encrypted private link to a confirmation form
- Closes the PR via PATCH /repos/{owner}/{repo}/pulls/{number} on
form submission
- Enqueues a sync task, sends a DM to the invoker, and posts to the
per-repo log thread (ZULIP_REPO_LOG) on success
New settings:
- ZULIP_CLOSE_PR_MUTATIONS_ENABLED (feature flag)
- ZULIP_REPO_LOG (per-repo stream/topic for bot action logging)
- ZULIP_CLOSE_PR_TOKEN_{SECRET,SALT,TTL_SECONDS,URL_BASE}
Requires queueboard-assignment GitHub App to be updated with:
- Pull requests: Read and write (upgrade)
- Members: Read (new org-level permission)
and close_pr added to GITHUB_APP_TOKEN_CONFIG operation_app_map.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- test_valid_open_pr_shows_confirmation_form: add ZULIP_CLOSE_PR_MUTATIONS_ENABLED override so the form renders instead of the preflight-disabled branch - test_github_close_error_shown_in_template: close_error holds exc.message (human-readable), not exc.code; check for "permission denied" substring instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Users may type `close_pr`, `assigned_prs`, or `pr_info` expecting them to work. The parser now replaces `_` with `-` in the command token before any lookup, so underscore variants resolve to the same command as their hyphen canonical names without needing per-command aliases. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d assigned-prs The parser now normalises underscores to hyphens before command lookup, making these aliases unnecessary. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mark implementation commits as complete, note the two follow-up fix commits, and add a new Operational Deployment Notes section covering the GitHub App permission upgrade steps, required env vars (GITHUB_APP_TOKEN_CONFIG, ZULIP_REPO_LOG, ZULIP_CLOSE_PR_MUTATIONS_ENABLED), and a manual verification checklist for post-deployment testing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d-org-read app Introduce a dedicated `check_collaborator_permission` operation so the `Members: Read` org-level permission can be carried by a separate `queueboard-org-read` GitHub App rather than `queueboard-assignment`. The execution service falls back to the `close_pr` token when `check_collaborator_permission` is not mapped, keeping single-app setups working without config changes. Update docs/github_app_setup.md and 041 design doc to reflect the three-app layout and new operation name. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…bled When ZULIP_CLOSE_PR_MUTATIONS_ENABLED is unset, the confirmation form now shows a dry-run warning and still allows submission. On POST it skips the actual close_pull_request() call but runs all post-actions (sync enqueue, DM, log post) normally, allowing a full end-to-end test without touching the PR. Template: replaces the old static preflight-only message with a dry-run callout on the form (GET) and a preflight_only success message (POST). View: restructures the POST guard so mutations_enabled controls only the close call, not the post-actions. Test updated accordingly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without a close-pr entry in ZULIP_COMMAND_POLICY the command is silently blocked for all users. Add step 4 with an example policy snippet and a pointer to manage.py zulip_policy. Also fix the duplicate step-3 numbering introduced in an earlier edit, and update the ZULIP_CLOSE_PR_MUTATIONS_ENABLED description to reflect the new dry-run behaviour. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the close-pr-specific ZULIP_CLOSE_PR_URL_BASE with the shared ZULIP_PREFS_URL_BASE that prefs and registration links already use. There is no reason to have separate base URL settings for different Zulip forms on the same server. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add ZULIP_CLOSE_PR_MUTATIONS_ENABLED and ZULIP_REPO_LOG entries. Update GITHUB_APP_TOKEN_CONFIG inline example to include close_pr and check_collaborator_permission operations and the queueboard-org-read app. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ZULIP_REPO_LOG was read via getattr(settings, ...) but never parsed from the env var in settings/base.py, so it remained a raw string and _resolve_repo_log_target silently returned None on every call. Add the same JSON-parse-and-validate pattern used by GITHUB_APP_TOKEN_CONFIG and ZULIP_COMMAND_POLICY. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Link the PR (markdown link to github.com) instead of bare backtick ref in both the DM and the log post. - Use a Zulip silent mention (@_**Name|id**) for the requester in the log post, looked up from User.zulip_full_name; falls back to `github_login` if the name is not populated. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…close-pr form Adds a textarea to the close-PR confirmation form where users can type a message that is posted as a GitHub comment before the PR is closed. Preset messages are loaded from .md files in close_pr_presets/ (first # heading = button label, remainder = body; sorted by filename). No code change is needed to add or edit presets. If the comment post fails the PR is not closed and an inline error is shown with the message preserved for retry. The error state now renders inline in the form rather than as a dead-end page in all cases. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…iry countdown - Show PR author, opened/updated dates, labels (colored chips), and body preview in a compact info card above the confirmation form. - Link all owner/repo#number occurrences to the GitHub PR URL. - Replace static "expires at" text with a live countdown that disables the submit button when the token expires, matching prefs-form behavior. Shared getExpiryState/formatRemaining logic imported from prefs_form.js. - Style close_pr_invalid.html with shared_pages.css for consistency. - Add close_pr_form.js (module) and move preset-button logic out of the inline <script> tag. - Add vitest suite for the new JS mount function. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ody display - Consolidate layout: PR title moves from hero into the PR card so all identity info (title, author, dates, labels, body) lives in one place; hero now shows only the page heading and owner/repo#number. - Remove attribution paragraph from the action card (PR card above makes the target obvious); keep only the bot-attribution warning. - Show full PR body with no character truncation or max-height cap. - Always show updated_at (dropped the same-day equality suppression). - Pass raw ISO strings (pr_opened_at_iso, pr_updated_at_iso) to template; JS formats <time class="ts" data-iso="..."> elements on load as "Apr 5, 2026, 2:34 PM (3 days ago)" using the user's local timezone. - Add formatTimestamp / formatAgo helpers to close_pr_form.js. - Expand frontend tests to cover timestamp formatting and .ts DOM wiring. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Restore max-height + overflow-y: auto on .pr-body so full text is accessible by scrolling without expanding the page. - Wrap pr-title in a link to the PR URL; colour inherits from heading so it stays visually prominent (underline on hover). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The env var was never loaded in settings/base.py, so getattr(settings, "ZULIP_CLOSE_PR_MUTATIONS_ENABLED", "") always returned "" and the form always showed dry-run mode regardless of the env var value. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two complementary fixes: 1. Post/Redirect/Get after successful POST: instead of rendering the success template directly (which leaves the browser on a POST URL), redirect to the same path with ?closed=1 or ?preflight=1. A browser refresh then replays the harmless GET, not the POST. 2. pr_is_open guard at POST entry: if the live PR state is already closed when a POST arrives (e.g. back-button resubmit), skip all mutations and post-actions and render the "already closed" view. Together these prevent a comment being posted twice, and the Zulip DM and repo-log entry being sent twice, on any form of POST replay. Update tests to expect 302 on successful POSTs and follow the redirect to assert success state; add test for the pr_is_open short-circuit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Makes the message useful in both the back-button resubmit case ("you
closed it just now, you're done") and the concurrent-close case
("closed by someone else, no action needed").
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… comment - DM: simplified to "Pull request X has been closed." with an optional ```quote block containing the close comment below. - Log: "PR X was closed by <user> on Apr 17, 2026, 14:32 UTC." with the same optional quote block; drops the "via bot, requested by" phrasing. - Add confirm() dialog on form submit when the close-message textarea is empty, guarding against accidental closes without a comment. - Expand frontend tests to cover the confirm dialog behaviour. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.
close-pr (something containing a URL to a PR)will DM the user a URL to a form that can trigger closure of PR using bot credentials.prepared with claude code