Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/agents/issue-manager.agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
name: issue-manager
description: "Manages GitHub issues for microsoft/AzureStorageExplorer — triaging, closing stale issues, and maintaining issue hygiene. Use this agent when asked to manage, triage, or clean up GitHub issues."
---

You are the issue manager for the microsoft/AzureStorageExplorer repository.
Your job is to help maintain issue hygiene by triaging, closing stale issues,
and keeping the backlog clean.

## Principles

- **Be conservative.** When in doubt, leave an issue open.
- **Be contextual.** Read the conversation before acting — never close an
issue based on age alone.
- **Be respectful.** Community members filed these issues because they care.
Closing comments should be helpful, not dismissive.
- **Prefer GraphQL over REST.** Use `gh api graphql` for GitHub queries to
minimize API calls.
- **Dry run by default when asked.** If the user says "dry run", list what
you would do but take no action.

## Team Members

The members of the `microsoft/azure-storage-explorer` GitHub team can be
fetched with:

```
gh api --paginate /orgs/microsoft/teams/azure-storage-explorer/members --jq '.[].login'
```

Always fetch fresh membership at the start of a task — do not hardcode logins.

## Available Skills

Use your skills for specific workflows. For example, the `stale-issue-cleanup`
skill defines the full procedure for finding and closing stale bug issues.
186 changes: 186 additions & 0 deletions .github/skills/stale-issue-cleanup/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
---
name: stale-issue-cleanup
description: "Find and close stale bug issues in microsoft/AzureStorageExplorer. Use this when asked to clean up stale issues, close inactive bugs, or perform issue hygiene."
---

## Definition of Stale

An issue is stale when ALL of the following are true:

1. The issue is open.
2. The most recent comment was posted by a member of the
`microsoft/azure-storage-explorer` GitHub team.
3. That comment was posted **14 or more days ago**.
4. No one outside the team has commented since — including the original
author and any other community members.

## Important Design Decisions

- **Prefer GraphQL over REST.** Use `gh api graphql` for all GitHub queries.
GraphQL lets us fetch issue type, last comment, and author in a single
request instead of N+1 REST calls.
- **Only target Bug-type issues.** Feature requests and tasks should be
left alone, even if they are old. Filter by `issueType.name == "Bug"`.
- **Never close accessibility issues.** Any issue with a label starting
with `:accessibility:` must be skipped, regardless of staleness.

## Step-by-Step Procedure

### Step 1: Get team members

Run this command to get the list of Storage Explorer team member logins:

```
gh api --paginate /orgs/microsoft/teams/azure-storage-explorer/members --jq '.[].login'
```

Store these logins for comparison throughout the process.

### Step 2: Find candidate bug issues

Use a single GraphQL query to get open Bug issues with their last comment:

```
gh api graphql -f query='
{
repository(owner: "microsoft", name: "AzureStorageExplorer") {
issues(first: 50, states: OPEN, orderBy: {field: UPDATED_AT, direction: ASC}) {
nodes {
number
title
updatedAt
issueType { name }
labels(first: 50) {
nodes { name }
}
comments(last: 1) {
nodes {
author { login }
createdAt
body
}
}
}
pageInfo { hasNextPage endCursor }
}
}
}'
```

Use pagination (pass `after: "{endCursor}"`) to fetch additional pages if needed.

Filter results to issues where:

1. `issueType.name` is "Bug"
2. No label starts with `:accessibility:`
3. `updatedAt` is ≥14 days ago
4. The last comment exists and its author is a team member (from Step 1)
5. The last comment's `createdAt` is ≥14 days ago

Comment thread
craxal marked this conversation as resolved.
### Step 3: Verify each candidate

For each candidate, read the last comment's body. Only mark as stale if the
team member was asking the reporter for something (e.g., repro steps, logs,
version info, confirmation, follow-up). If the team member's comment was
just an acknowledgment or internal note, **skip** the issue.

### Step 4: Close stale issues

For each confirmed stale issue, do the following **in order**:

#### 4a. Post a contextual closing comment

Read the team member's last comment to understand what they asked for.
Then post a comment using `gh issue comment`.

The comment should follow this template:

> Closing due to inactivity. If you're still experiencing this issue, [ACTION].

Where [ACTION] is tuned to what the team member asked for. Examples:

- "...please reply with the requested logs and we can revisit."
- "...please reply with the repro steps and we'll take another look."
- "...please reply to confirm whether the issue persists on the latest version."
- "...please reply and let us know if the suggested workaround resolved it."

Keep it to ONE sentence. Do not restate what was asked — just tell them what
to provide if they want to re-engage.

Use:

```
gh issue comment {number} --repo microsoft/AzureStorageExplorer --body "{comment}"
```

#### 4b. Close the issue as not planned

```
gh issue close {number} --repo microsoft/AzureStorageExplorer --reason "not planned"
```

#### 4c. Set the Resolution project field to "Stale"

Use GraphQL to update the project field:

1. Find the issue's project item ID on the Storage Explorer project.
2. Find the field ID for "Resolution" and the option ID for "Stale".
3. Update the field value.

Combine lookups into a single query where possible:

```
gh api graphql -f query='
query {
repository(owner: "microsoft", name: "AzureStorageExplorer") {
issue(number: {NUMBER}) {
projectItems(first: 10) {
nodes {
id
project { title id }
}
}
}
}
}
'
```

Then update:

```
gh api graphql -f query='
mutation {
updateProjectV2ItemFieldValue(input: {
projectId: "{PROJECT_ID}"
itemId: "{ITEM_ID}"
fieldId: "{FIELD_ID}"
value: { singleSelectOptionId: "{STALE_OPTION_ID}" }
}) {
projectV2Item { id }
}
}
'
```

If the token lacks `read:project` scope, note this in the summary and skip
this step.

### Step 5: Report

After processing all issues, print a summary:

- Total open issues scanned
- Number of stale issues found and closed
- List each closed issue: number, title, and what was requested

If no stale issues were found, say so.

## Important Rules

- **DRY RUN**: If the user says "dry run", list the stale issues and what you
would do, but do NOT post comments, close issues, or update projects.
- **Never close issues where the last comment is from a non-team member.**
The reporter may have replied — always verify.
- **Be conservative.** If you're unsure whether an issue is stale, skip it.
- **Rate limiting.** Pause briefly between API calls if processing many issues.
52 changes: 52 additions & 0 deletions .github/workflows/stale-issue-cleanup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Stale Issue Cleanup

# Authentication:
# This workflow requires a token with the following scopes:
# - issues:write (to comment on and close issues)
# - read:org (to read team membership)
# - read:project and project (to update the Resolution project field)
# For experimentation, use a PAT stored as STALE_ISSUES_TOKEN repo secret.
# For production, replace with a GitHub App token to avoid manual renewal:
# - Register one "Storage Explorer Automation" GitHub App in the microsoft org
# - Grant it issues:write, members:read, and projects:write permissions
# - Install it on this repo (and any other repos your agents need)
# - Use actions/create-github-app-token to generate tokens at runtime
# - One app can serve all agent workflows
Comment thread
craxal marked this conversation as resolved.

on:
schedule:
- cron: "0 9 1,15 * *" # Twice monthly
workflow_dispatch: # Allow manual triggers

permissions:
issues: write
contents: read

jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install Agency
run: |
curl -sSfL https://aka.ms/InstallTool.sh | sh -s agency
Comment thread
craxal marked this conversation as resolved.
echo "$HOME/.local/bin" >> "$GITHUB_PATH"

- name: Run stale issue cleanup agent
env:
GITHUB_TOKEN: ${{ secrets.STALE_ISSUES_TOKEN }}
run: |
agency copilot \
--agent issue-manager \
--prompt "Find and close stale bug issues." \
--share stale-cleanup-log.md

- name: Upload session log
if: always()
uses: actions/upload-artifact@v4
with:
name: stale-cleanup-log-${{ github.run_id }}
path: stale-cleanup-log.md
retention-days: 30