@sentry/junior-github 0.199.0 → 0.201.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-6OPIFILN.js → chunk-F2PJG6AF.js} +31 -2
- package/dist/index.js +576 -177
- package/dist/resource-events/pull-request.d.ts +2 -0
- package/dist/testing.js +1 -1
- package/dist/tools/submit-pull-request-review.d.ts +42 -0
- package/dist/tools/update-pull-request-feedback.d.ts +59 -0
- package/dist/webhooks/handler.d.ts +7 -0
- package/package.json +2 -2
- package/skills/github-code/SKILL.md +25 -24
- package/skills/github-code/references/api-surface.md +42 -40
- package/skills/github-code/references/troubleshooting-workarounds.md +28 -29
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type SubscribableResource } from "@sentry/junior-plugin-api";
|
|
2
2
|
export declare const GITHUB_PULL_REQUEST_EVENTS: readonly ["pull_request.checks.failed", "pull_request.checks.recovered", "pull_request.comment.created", "pull_request.opened", "pull_request.ready_for_review", "pull_request.review.approved", "pull_request.review.changes_requested", "pull_request.review.commented", "pull_request.review_comment.created", "pull_request.merged", "pull_request.closed_unmerged"];
|
|
3
3
|
export type GitHubPullRequestEvent = (typeof GITHUB_PULL_REQUEST_EVENTS)[number];
|
|
4
|
+
/** Combine provider defaults with app guidance for pull request events. */
|
|
5
|
+
export declare function gitHubPullRequestEventGuidance(guidance?: Partial<Record<GitHubPullRequestEvent, string>>): Partial<Record<GitHubPullRequestEvent, string>>;
|
|
4
6
|
/** App-configured pull request resource event behavior. */
|
|
5
7
|
export interface GitHubPullRequestEventOptions {
|
|
6
8
|
/** App guidance applied within the matching subscription or event task instruction. */
|
package/dist/testing.js
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type PluginEgress } from "@sentry/junior-plugin-api";
|
|
2
|
+
/** Submit a comment or change-request review through GitHub's REST API. */
|
|
3
|
+
export declare function createGitHubSubmitPullRequestReviewTool(egress: PluginEgress): import("@sentry/junior-plugin-api").PluginToolDefinition<{
|
|
4
|
+
repo: string;
|
|
5
|
+
number: number;
|
|
6
|
+
event: "COMMENT" | "REQUEST_CHANGES";
|
|
7
|
+
body: string;
|
|
8
|
+
comments?: {
|
|
9
|
+
path: string;
|
|
10
|
+
body: string;
|
|
11
|
+
line: number;
|
|
12
|
+
side: "LEFT" | "RIGHT";
|
|
13
|
+
startLine?: number | undefined;
|
|
14
|
+
startSide?: "LEFT" | "RIGHT" | undefined;
|
|
15
|
+
}[] | undefined;
|
|
16
|
+
}, {
|
|
17
|
+
[x: string]: unknown;
|
|
18
|
+
target: "submitPullRequestReview";
|
|
19
|
+
repo: string;
|
|
20
|
+
number: number;
|
|
21
|
+
reviewId: number;
|
|
22
|
+
state: string;
|
|
23
|
+
url: string;
|
|
24
|
+
truncated?: boolean | undefined;
|
|
25
|
+
continuation?: {
|
|
26
|
+
arguments: Record<string, unknown>;
|
|
27
|
+
reason?: string | undefined;
|
|
28
|
+
} | undefined;
|
|
29
|
+
}, {
|
|
30
|
+
[x: string]: unknown;
|
|
31
|
+
target: "submitPullRequestReview";
|
|
32
|
+
repo: string;
|
|
33
|
+
number: number;
|
|
34
|
+
reviewId: number;
|
|
35
|
+
state: string;
|
|
36
|
+
url: string;
|
|
37
|
+
truncated?: boolean | undefined;
|
|
38
|
+
continuation?: {
|
|
39
|
+
arguments: Record<string, unknown>;
|
|
40
|
+
reason?: string | undefined;
|
|
41
|
+
} | undefined;
|
|
42
|
+
}>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type PluginEgress } from "@sentry/junior-plugin-api";
|
|
2
|
+
/**
|
|
3
|
+
* GitHub has reactions but no feedback state. This tool maps each state to a
|
|
4
|
+
* reaction and keeps at most one state reaction that Junior owns. It does not
|
|
5
|
+
* change reactions from other users.
|
|
6
|
+
*/
|
|
7
|
+
declare const STATUS_TO_REACTION_CONTENT: {
|
|
8
|
+
readonly reviewing: "eyes";
|
|
9
|
+
readonly addressed: "+1";
|
|
10
|
+
readonly declined: "-1";
|
|
11
|
+
};
|
|
12
|
+
type FeedbackStatus = keyof typeof STATUS_TO_REACTION_CONTENT;
|
|
13
|
+
type PullRequestCommentKind = "conversation" | "review";
|
|
14
|
+
/** Build the GitHub API path for one pull request comment's reactions. */
|
|
15
|
+
export declare function pullRequestCommentReactionsPath(input: {
|
|
16
|
+
owner: string;
|
|
17
|
+
name: string;
|
|
18
|
+
commentKind: PullRequestCommentKind;
|
|
19
|
+
commentId: number;
|
|
20
|
+
}): string;
|
|
21
|
+
/** Return the GitHub reaction for one feedback state. */
|
|
22
|
+
export declare function pullRequestFeedbackReactionContent(status: FeedbackStatus): (typeof STATUS_TO_REACTION_CONTENT)[FeedbackStatus];
|
|
23
|
+
/**
|
|
24
|
+
* Set Junior's feedback reaction on a pull request comment. Replace only
|
|
25
|
+
* state reactions Junior previously added to the same comment.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createGitHubUpdatePullRequestFeedbackTool(ctx: {
|
|
28
|
+
egress: PluginEgress;
|
|
29
|
+
}, botEmail: string | undefined): import("@sentry/junior-plugin-api").PluginToolDefinition<{
|
|
30
|
+
repo: string;
|
|
31
|
+
commentKind: "conversation" | "review";
|
|
32
|
+
commentId: number;
|
|
33
|
+
status: "reviewing" | "addressed" | "declined";
|
|
34
|
+
}, {
|
|
35
|
+
[x: string]: unknown;
|
|
36
|
+
target: "updatePullRequestFeedback";
|
|
37
|
+
repo: string;
|
|
38
|
+
commentId: number;
|
|
39
|
+
status: "reviewing" | "addressed" | "declined";
|
|
40
|
+
reactionId: number;
|
|
41
|
+
truncated?: boolean | undefined;
|
|
42
|
+
continuation?: {
|
|
43
|
+
arguments: Record<string, unknown>;
|
|
44
|
+
reason?: string | undefined;
|
|
45
|
+
} | undefined;
|
|
46
|
+
}, {
|
|
47
|
+
[x: string]: unknown;
|
|
48
|
+
target: "updatePullRequestFeedback";
|
|
49
|
+
repo: string;
|
|
50
|
+
commentId: number;
|
|
51
|
+
status: "reviewing" | "addressed" | "declined";
|
|
52
|
+
reactionId: number;
|
|
53
|
+
truncated?: boolean | undefined;
|
|
54
|
+
continuation?: {
|
|
55
|
+
arguments: Record<string, unknown>;
|
|
56
|
+
reason?: string | undefined;
|
|
57
|
+
} | undefined;
|
|
58
|
+
}>;
|
|
59
|
+
export {};
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { CodeChangePublisher, PluginConversationAnnotations, PluginLogger, PluginRoute, ResourceEventPublisher } from "@sentry/junior-plugin-api";
|
|
2
2
|
import type { GitHubDb } from "../db/database.js";
|
|
3
3
|
import type { GitHubPullRequestCommitComposition } from "../db/schema.js";
|
|
4
|
+
interface FeedbackTarget {
|
|
5
|
+
commentId: number;
|
|
6
|
+
commentKind: "conversation" | "review";
|
|
7
|
+
repo: string;
|
|
8
|
+
}
|
|
4
9
|
/** Create the public, signed GitHub webhook route owned by the plugin. */
|
|
5
10
|
export declare function createGitHubWebhookRoute(args: {
|
|
6
11
|
annotations: PluginConversationAnnotations;
|
|
@@ -15,7 +20,9 @@ export declare function createGitHubWebhookRoute(args: {
|
|
|
15
20
|
installationId(): string | undefined;
|
|
16
21
|
installationIdEnv: string;
|
|
17
22
|
log?: Pick<PluginLogger, "error">;
|
|
23
|
+
markFeedbackReviewing(input: FeedbackTarget): Promise<void>;
|
|
18
24
|
privateKeyEnv: string;
|
|
19
25
|
resourceEvents: ResourceEventPublisher;
|
|
20
26
|
webhookSecret(): string | undefined;
|
|
21
27
|
}): PluginRoute;
|
|
28
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/junior-github",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.201.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@sinclair/typebox": "^0.34.49",
|
|
32
32
|
"drizzle-orm": "^0.45.2",
|
|
33
33
|
"zod": "^4.5.4",
|
|
34
|
-
"@sentry/junior-plugin-api": "0.
|
|
34
|
+
"@sentry/junior-plugin-api": "0.201.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@oxlint/plugins": "1.79.0",
|
|
@@ -3,36 +3,37 @@ name: github-code
|
|
|
3
3
|
description: Work with GitHub repositories, source code, branches, commits, pull requests, reviews, diffs, CI, and repository credentials. Use for implementation, source investigation, clone/fetch/branch workflows, PR creation or updates, review feedback, GitHub Actions checks, and repository permission failures. Prefer this skill for repository tasks even when they concern a Sentry product.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# GitHub
|
|
6
|
+
# GitHub code
|
|
7
7
|
|
|
8
8
|
Use `git` and `gh` for repository work.
|
|
9
9
|
|
|
10
|
-
| Action
|
|
11
|
-
|
|
|
12
|
-
| Create PR
|
|
13
|
-
| Update PR title/body/base/state | `github_updatePullRequest`
|
|
14
|
-
|
|
|
15
|
-
|
|
|
10
|
+
| Action | Tool / command |
|
|
11
|
+
| ------------------------------- | ------------------------------------------------------------------------ |
|
|
12
|
+
| Create PR | `github_createPullRequest` (not `gh pr create`) |
|
|
13
|
+
| Update PR title/body/base/state | `github_updatePullRequest` |
|
|
14
|
+
| Submit PR review | `github_submitPullRequestReview` |
|
|
15
|
+
| Resolve review thread | `github_resolvePullRequestReviewThread` |
|
|
16
|
+
| Clone missing repo | `github_cloneRepository`; on Workspace match error use `switchWorkspace` |
|
|
16
17
|
|
|
17
18
|
## Open when needed
|
|
18
19
|
|
|
19
|
-
| Need
|
|
20
|
-
|
|
|
21
|
-
| Commands
|
|
22
|
-
| Edit
|
|
23
|
-
|
|
|
20
|
+
| Need | Read |
|
|
21
|
+
| ----------------------------- | -------------------------------------------------------------------------------------- |
|
|
22
|
+
| Commands and permissions | [references/api-surface.md](references/api-surface.md) |
|
|
23
|
+
| Edit, verify, and open a PR | [references/workflow.md](references/workflow.md) |
|
|
24
|
+
| Command or permission failure | [references/troubleshooting-workarounds.md](references/troubleshooting-workarounds.md) |
|
|
24
25
|
|
|
25
26
|
## Always
|
|
26
27
|
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
- Read applicable `AGENTS.md` before
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
- Push
|
|
33
|
-
-
|
|
34
|
-
- Stop for
|
|
35
|
-
- Unless the user opts out,
|
|
36
|
-
- Report
|
|
37
|
-
|
|
38
|
-
Do not install or repair the GitHub plugin
|
|
28
|
+
- Use the requested repo. Otherwise read `github.repo` with standalone `jr-rpc config get github.repo`.
|
|
29
|
+
- Pass `--repo owner/repo` to `gh`. Use `git -C PATH` for local repos.
|
|
30
|
+
- Read each applicable `AGENTS.md` before edits.
|
|
31
|
+
- Keep unrelated work. Never force-push, delete refs, or make destructive merges.
|
|
32
|
+
- Use repository evidence. Report only checks you ran.
|
|
33
|
+
- Push before `github_createPullRequest`. Never ask for a token for bot pushes.
|
|
34
|
+
- When an error names a tool, use it. Change permissions only when GitHub rejected the request.
|
|
35
|
+
- Stop for an unclear target, missing access, destructive work, or a GitHub permission failure.
|
|
36
|
+
- Unless the user opts out, push completed work and open a draft PR.
|
|
37
|
+
- Report repo, branch, PR, checks, and skipped checks.
|
|
38
|
+
|
|
39
|
+
Do not install or repair the GitHub plugin from this skill.
|
|
@@ -1,56 +1,58 @@
|
|
|
1
|
-
# GitHub
|
|
1
|
+
# GitHub commands and permissions
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use the tools in `SKILL.md` to create or update a PR, submit a review, or resolve a review thread. Use supported REST endpoints for other writes. Do not use GraphQL mutations.
|
|
4
4
|
|
|
5
5
|
## Repo targeting
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
If the user omits `owner/repo`, run `jr-rpc config get github.repo`. Then pass `--repo owner/repo` to `gh`.
|
|
8
8
|
|
|
9
9
|
## Permissions
|
|
10
10
|
|
|
11
|
-
| Capability
|
|
12
|
-
|
|
|
13
|
-
| `github.actions.read`
|
|
14
|
-
| `github.actions.write`
|
|
15
|
-
| `github.contents.read`
|
|
16
|
-
| `github.contents.write`
|
|
17
|
-
| `github.workflows.write`
|
|
18
|
-
| `github.pull-requests.read`
|
|
19
|
-
| `github.pull-requests.write` |
|
|
11
|
+
| Capability | Commands |
|
|
12
|
+
| ---------------------------- | ------------------------------------------------------------------------------------ |
|
|
13
|
+
| `github.actions.read` | `gh run list`, `gh run view`, `gh run watch`, `gh workflow list`, `gh workflow view` |
|
|
14
|
+
| `github.actions.write` | `gh workflow run`, `gh run rerun`, `gh run cancel` |
|
|
15
|
+
| `github.contents.read` | `gh repo clone`, `git fetch` |
|
|
16
|
+
| `github.contents.write` | Git smart-HTTP `git push` only |
|
|
17
|
+
| `github.workflows.write` | Workflow-file changes on push |
|
|
18
|
+
| `github.pull-requests.read` | `gh pr view`, `gh pr list`, `gh pr diff`, `gh pr checks` |
|
|
19
|
+
| `github.pull-requests.write` | GitHub tools and supported REST writes |
|
|
20
20
|
|
|
21
21
|
## Commands
|
|
22
22
|
|
|
23
|
-
| Operation
|
|
24
|
-
|
|
|
25
|
-
| Clone (default shallow) | `gh repo clone owner/repo [DIR] -- --depth=1`
|
|
26
|
-
| Fetch bounded base
|
|
27
|
-
| Deepen base
|
|
28
|
-
| Unshallow
|
|
29
|
-
| Shallow check
|
|
30
|
-
| Branch / status
|
|
31
|
-
| Log / diff vs base
|
|
32
|
-
| Default branch
|
|
33
|
-
| Create branch
|
|
34
|
-
| Commit
|
|
35
|
-
| Push
|
|
36
|
-
| Workflow dispatch
|
|
37
|
-
| Rerun / cancel
|
|
38
|
-
| Ready for review
|
|
39
|
-
| Request reviewers
|
|
40
|
-
| Submit review
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
23
|
+
| Operation | Command |
|
|
24
|
+
| ----------------------- | ----------------------------------------------------------------------------------------------- |
|
|
25
|
+
| Clone (default shallow) | `gh repo clone owner/repo [DIR] -- --depth=1` |
|
|
26
|
+
| Fetch bounded base | `git -C DIR fetch --depth=N origin BASE:refs/remotes/origin/BASE` |
|
|
27
|
+
| Deepen base | `git -C DIR fetch --deepen=N origin BASE:refs/remotes/origin/BASE` |
|
|
28
|
+
| Unshallow | `git -C DIR fetch --unshallow origin` |
|
|
29
|
+
| Shallow check | `git -C DIR rev-parse --is-shallow-repository` |
|
|
30
|
+
| Branch / status | `git -C DIR branch --show-current` / `git -C DIR status --short --branch` |
|
|
31
|
+
| Log / diff vs base | `git -C DIR log origin/BASE..HEAD --oneline` / `git -C DIR diff origin/BASE...HEAD` |
|
|
32
|
+
| Default branch | `gh repo view owner/repo --json defaultBranchRef --jq .defaultBranchRef.name` |
|
|
33
|
+
| Create branch | `git -C DIR checkout -b BRANCH` |
|
|
34
|
+
| Commit | `git -C DIR add -A && git -C DIR commit -m "message"` |
|
|
35
|
+
| Push | `git -C DIR push -u origin BRANCH` |
|
|
36
|
+
| Workflow dispatch | `gh workflow run WORKFLOW --repo owner/repo --ref REF [-f key=value]` |
|
|
37
|
+
| Rerun / cancel | `gh run rerun RUN_ID -R owner/repo [--failed]` / `gh run cancel RUN_ID -R owner/repo` |
|
|
38
|
+
| Ready for review | `gh api repos/owner/repo/pulls/NUMBER/ready_for_review --method POST` |
|
|
39
|
+
| Request reviewers | `gh api repos/owner/repo/pulls/NUMBER/requested_reviewers --method POST --input reviewers.json` |
|
|
40
|
+
| Submit review | `github_submitPullRequestReview` |
|
|
41
|
+
| Set feedback status | `github_updatePullRequestFeedback` |
|
|
42
|
+
| Resolve review thread | `github_resolvePullRequestReviewThread` |
|
|
43
|
+
| Inline review comment | `gh api repos/owner/repo/pulls/NUMBER/comments --method POST --input comment.json` |
|
|
44
|
+
| View PR / checks | `gh pr view NUMBER --repo owner/repo` / `gh pr checks NUMBER --repo owner/repo` |
|
|
45
|
+
| Diff PR | `gh pr diff NUMBER --repo owner/repo` |
|
|
46
|
+
| List runs | `gh run list -R owner/repo --workflow WORKFLOW` |
|
|
47
|
+
| View / watch run | `gh run view RUN_ID -R owner/repo` / `gh run watch RUN_ID -R owner/repo --exit-status` |
|
|
46
48
|
|
|
47
49
|
## Notes
|
|
48
50
|
|
|
49
51
|
- Prefer `--json` where available. Pass clone flags after `--`.
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
- Push head and
|
|
53
|
-
-
|
|
54
|
-
- Reviews and inline comments
|
|
52
|
+
- A local commit does not call GitHub. A push uses installation credentials. Workflow changes also need `workflows.write`.
|
|
53
|
+
- Deepen a shallow clone before work that needs old history. Do not force-push to work around missing history.
|
|
54
|
+
- Push `head` and read the default `base` before `github_createPullRequest`.
|
|
55
|
+
- Junior does not support merges, forks, repository administration, REST content or Git database writes, direct PR update or review writes, or GraphQL mutations.
|
|
56
|
+
- Reviews and inline comments use the App bot.
|
|
55
57
|
- PR comments/labels/assignees use issue endpoints; load `github-issues` for those.
|
|
56
58
|
- Embed local images with `publishImage` first (public URL). Do not use private Slack file links.
|
|
@@ -1,33 +1,32 @@
|
|
|
1
|
-
# GitHub
|
|
1
|
+
# GitHub troubleshooting
|
|
2
2
|
|
|
3
|
-
|
|
|
4
|
-
|
|
|
5
|
-
| `
|
|
6
|
-
|
|
|
7
|
-
|
|
|
8
|
-
|
|
|
9
|
-
|
|
|
10
|
-
| 401
|
|
11
|
-
| `
|
|
12
|
-
|
|
|
13
|
-
| `
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
| `dnf install gh failed` | Plugin bootstrap | Report runtime bootstrap failure; do not repair from skill |
|
|
3
|
+
| Problem | Action |
|
|
4
|
+
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
5
|
+
| `gh` reports an unknown command | Report that the GitHub plugin is unavailable. Do not install it. |
|
|
6
|
+
| Clone rejects `--depth` | Put clone flags after `--`: `gh repo clone owner/repo -- --depth=1`. |
|
|
7
|
+
| Repository is missing or wrong | Read `github.repo`, then pass `--repo owner/repo`. |
|
|
8
|
+
| GraphQL cannot find the repository | Check `owner/repo` and the GitHub App installation. |
|
|
9
|
+
| `junior-auth-required` with `user-write` | Follow the private OAuth prompt. Never ask for a pasted token. |
|
|
10
|
+
| `git push` returns 401 or 403 | Check the remote and repository. Retry once, then report the installation access failure. |
|
|
11
|
+
| `permission_denied` has `source: "upstream"` | GitHub rejected the request. Report the grant, account, or SSO details. |
|
|
12
|
+
| Other 403 response | Read the error and use the tool it names. |
|
|
13
|
+
| `gh auth status` shows no token scopes | This is normal for GitHub App user tokens. Check App permissions instead. |
|
|
14
|
+
| `github_createPullRequest` returns 401 or 403 | Report the installation access failure. Do not use user OAuth. |
|
|
15
|
+
| PR creation returns 422 for `head` | Push the branch, then retry with explicit `head` and `base`. |
|
|
16
|
+
| PR create or update returns 422 for `base` | Read the default branch, then retry. |
|
|
17
|
+
| 403 names `github_updatePullRequest` | Use `github_updatePullRequest`. |
|
|
18
|
+
| PR review reports blocked GraphQL mutations | Use `github_submitPullRequestReview`. |
|
|
19
|
+
| Review-thread resolution reports blocked GraphQL mutations | Use `github_resolvePullRequestReviewThread`. It works only on bot-authored PRs. |
|
|
20
|
+
| Blame or old history is missing | Deepen the needed refs. Unshallow only when required. |
|
|
21
|
+
| Rebase has missing ancestry | Fetch `BASE:refs/remotes/origin/BASE`, deepen it, and use `origin/BASE`. |
|
|
22
|
+
| Dependencies are missing | Run the frozen install. Do not change the lockfile. |
|
|
23
|
+
| Frozen install fails | Report the exact failure. |
|
|
24
|
+
| Plugin setup fails | Report the setup failure. Do not repair it from the skill. |
|
|
26
25
|
|
|
27
26
|
## Retry rules
|
|
28
27
|
|
|
29
|
-
- Retry
|
|
30
|
-
- Do not
|
|
31
|
-
- `user-read
|
|
32
|
-
-
|
|
33
|
-
-
|
|
28
|
+
- Retry a transport failure once after you check the repository.
|
|
29
|
+
- Do not repeat 401, 403, 404, or validation failures.
|
|
30
|
+
- A `user-read` or `user-write` failure needs private App OAuth.
|
|
31
|
+
- An `installation-*` failure needs an App permission or installation fix.
|
|
32
|
+
- Use structured `permission_denied` details when present.
|