@sentry/junior-github 0.69.0 → 0.71.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/SETUP.md +7 -4
- package/index.js +139 -6
- package/package.json +2 -2
- package/skills/github-code/SKILL.md +2 -3
- package/skills/github-code/references/troubleshooting-workarounds.md +3 -2
- package/skills/github-issues/SKILL.md +3 -4
- package/skills/github-issues/references/issue-examples.md +0 -28
- package/skills/github-issues/references/troubleshooting-workarounds.md +13 -12
package/SETUP.md
CHANGED
|
@@ -66,19 +66,22 @@ Repeat for `preview` and `development` as needed. After env changes, redeploy so
|
|
|
66
66
|
|
|
67
67
|
### Optional permission overrides
|
|
68
68
|
|
|
69
|
-
By default, `installation-read` grants read the app installation's current permission envelope once per process and scopes read-capable permissions down to `read` when requesting a token. To declare the
|
|
69
|
+
By default, `installation-read` grants read the app installation's current permission envelope once per process and scopes read-capable permissions down to `read` when requesting a token. To declare the GitHub App permission envelope in the plugin and avoid that installation lookup, pass `appPermissions` when registering the plugin:
|
|
70
70
|
|
|
71
71
|
```ts
|
|
72
72
|
githubPlugin({
|
|
73
73
|
appPermissions: {
|
|
74
|
-
|
|
74
|
+
actions: "write",
|
|
75
|
+
contents: "write",
|
|
75
76
|
issues: "write",
|
|
77
|
+
metadata: "read",
|
|
76
78
|
pull_requests: "write",
|
|
79
|
+
workflows: "write",
|
|
77
80
|
},
|
|
78
81
|
});
|
|
79
82
|
```
|
|
80
83
|
|
|
81
|
-
Junior records these permissions as plugin capabilities. Installation-read token requests remain read-only by requesting read-capable configured permissions at `read` level and omitting GitHub permission fields that have no `read` value. GitHub remains the source of truth for whether a permission name or level exists.
|
|
84
|
+
Junior records these permissions as plugin capabilities. The configured values are the maximum GitHub App envelope Junior may need for writes. Installation-read token requests remain read-only by requesting read-capable configured permissions at `read` level and omitting GitHub permission fields that have no `read` value. GitHub remains the source of truth for whether a permission name or level exists.
|
|
82
85
|
|
|
83
86
|
GitHub App user-to-server tokens do not use OAuth scopes as their permission model. Their effective access is limited by the GitHub App's installed permissions, the app installation's repository access, and the requesting user's own GitHub access. GitHub returns an empty `scope` value for these tokens, so Junior cannot verify granted scopes from the token response.
|
|
84
87
|
|
|
@@ -95,7 +98,7 @@ Use `additionalUserScopes` only when an integration flow requires specific GitHu
|
|
|
95
98
|
## 3) Runtime behavior
|
|
96
99
|
|
|
97
100
|
- When either GitHub skill is active, authenticated `gh` and `git` commands cause the runtime to inject GitHub credentials automatically for the current turn.
|
|
98
|
-
- The plugin classifies GitHub traffic from the forwarded HTTP request. Safe app-readable API methods, GraphQL `GET`/`HEAD`/`OPTIONS` requests, and `git-upload-pack` use the `installation-read` grant. `GET /user` uses the `user-read` grant so account identity checks use the requester token. Write-specific REST URLs,
|
|
101
|
+
- The plugin classifies GitHub traffic from the forwarded HTTP request. Safe app-readable API methods, GraphQL `GET`/`HEAD`/`OPTIONS` requests, GraphQL `POST` bodies that prove the operation is a query, and `git-upload-pack` use the `installation-read` grant. `GET /user` uses the `user-read` grant so account identity checks use the requester token. Write-specific REST URLs, GraphQL mutations/subscriptions, unknown GraphQL `POST` bodies, other non-read API methods, and `git-receive-pack` use the `user-write` grant.
|
|
99
102
|
- `user-read` and `user-write` require the requester, or an explicitly delegated user subject from an allowed system run, to authorize the GitHub App through the private OAuth flow. Missing or expired user authorization pauses interactive turns, sends a private authorization link, and resumes after approval.
|
|
100
103
|
- Git commits use the requester as the commit author, Junior as committer, and a Junior `Co-Authored-By` trailer.
|
|
101
104
|
- Issued credentials are reused only within the current turn, credential leases are cached separately by plugin grant name, and upstream 403 permission denials clear the cached lease before the next retry.
|
package/index.js
CHANGED
|
@@ -13,6 +13,7 @@ const GITHUB_AUTH_TOKEN_ENV = "GITHUB_TOKEN";
|
|
|
13
13
|
const GITHUB_AUTH_TOKEN_PLACEHOLDER = "ghp_host_managed_credential";
|
|
14
14
|
const MAX_LEASE_MS = 60 * 60 * 1000;
|
|
15
15
|
const REFRESH_BUFFER_MS = 5 * 60 * 1000;
|
|
16
|
+
const GITHUB_GRAPHQL_RESPONSE_BODY_LIMIT_BYTES = 64 * 1024;
|
|
16
17
|
const HTTP_READ_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
|
|
17
18
|
const USER_TOKEN_GRANTS = new Set(["user-read", "user-write"]);
|
|
18
19
|
const CONTENTS_WRITE_REQUIREMENTS = [
|
|
@@ -701,20 +702,133 @@ function githubUserReadReason(method, upstreamUrl) {
|
|
|
701
702
|
: undefined;
|
|
702
703
|
}
|
|
703
704
|
|
|
704
|
-
function
|
|
705
|
+
function parseGitHubGraphqlOperation(bodyText) {
|
|
706
|
+
if (typeof bodyText !== "string" || bodyText.trim().length === 0) {
|
|
707
|
+
return undefined;
|
|
708
|
+
}
|
|
709
|
+
let parsed;
|
|
710
|
+
try {
|
|
711
|
+
parsed = JSON.parse(bodyText);
|
|
712
|
+
} catch {
|
|
713
|
+
return undefined;
|
|
714
|
+
}
|
|
715
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
716
|
+
return undefined;
|
|
717
|
+
}
|
|
718
|
+
const query = parsed.query;
|
|
719
|
+
if (typeof query !== "string") {
|
|
720
|
+
return undefined;
|
|
721
|
+
}
|
|
722
|
+
const operationName =
|
|
723
|
+
typeof parsed.operationName === "string"
|
|
724
|
+
? parsed.operationName.trim()
|
|
725
|
+
: undefined;
|
|
726
|
+
const normalized = maskGraphqlStringLiterals(
|
|
727
|
+
query.replace(/^\s*#[^\n\r]*(?:\r?\n|$)/gm, ""),
|
|
728
|
+
).trim();
|
|
729
|
+
if (operationName) {
|
|
730
|
+
const namedOperation = normalized.match(
|
|
731
|
+
new RegExp(
|
|
732
|
+
`\\b(query|mutation|subscription)\\s+${escapeRegExp(operationName)}\\b`,
|
|
733
|
+
),
|
|
734
|
+
)?.[1];
|
|
735
|
+
return namedOperation ? graphqlOperationAccess(namedOperation) : undefined;
|
|
736
|
+
}
|
|
737
|
+
const operation = normalized.match(/\b(query|mutation|subscription)\b/)?.[1];
|
|
738
|
+
const operationAccess = graphqlOperationAccess(operation);
|
|
739
|
+
if (operationAccess) {
|
|
740
|
+
return operationAccess;
|
|
741
|
+
}
|
|
742
|
+
if (normalized.startsWith("{")) {
|
|
743
|
+
return "read";
|
|
744
|
+
}
|
|
745
|
+
return undefined;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
function escapeRegExp(value) {
|
|
749
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function graphqlOperationAccess(operation) {
|
|
753
|
+
if (operation === "mutation" || operation === "subscription") {
|
|
754
|
+
return "write";
|
|
755
|
+
}
|
|
756
|
+
if (operation === "query") {
|
|
757
|
+
return "read";
|
|
758
|
+
}
|
|
759
|
+
return undefined;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function maskGraphqlStringLiterals(query) {
|
|
763
|
+
return query.replace(/"""[\s\S]*?"""|"(?:\\.|[^"\\])*"/g, (match) =>
|
|
764
|
+
" ".repeat(match.length),
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function githubGraphqlAccess(method, upstreamUrl, bodyText) {
|
|
705
769
|
if (!isGitHubGraphqlUrl(upstreamUrl)) {
|
|
706
770
|
return undefined;
|
|
707
771
|
}
|
|
708
772
|
if (HTTP_READ_METHODS.has(method)) {
|
|
709
773
|
return "read";
|
|
710
774
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
775
|
+
const operation = parseGitHubGraphqlOperation(bodyText);
|
|
776
|
+
if (operation) {
|
|
777
|
+
return operation;
|
|
778
|
+
}
|
|
779
|
+
// Unknown GraphQL POST bodies still require user-write attribution rather
|
|
780
|
+
// than risking an unattributed mutation through an installation-read token.
|
|
715
781
|
return "write";
|
|
716
782
|
}
|
|
717
783
|
|
|
784
|
+
function githubGraphqlPermissionDeniedMessage(bodyText) {
|
|
785
|
+
let parsed;
|
|
786
|
+
try {
|
|
787
|
+
parsed = JSON.parse(bodyText);
|
|
788
|
+
} catch {
|
|
789
|
+
return undefined;
|
|
790
|
+
}
|
|
791
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.errors)) {
|
|
792
|
+
return undefined;
|
|
793
|
+
}
|
|
794
|
+
for (const error of parsed.errors) {
|
|
795
|
+
if (!isRecord(error) || typeof error.message !== "string") {
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
const message = error.message;
|
|
799
|
+
if (
|
|
800
|
+
error.type === "NOT_FOUND" &&
|
|
801
|
+
/\bCould not resolve to a Repository with the name\b/.test(message)
|
|
802
|
+
) {
|
|
803
|
+
return `GitHub GraphQL could not access the repository: ${message}`;
|
|
804
|
+
}
|
|
805
|
+
if (/\bResource not accessible by integration\b/.test(message)) {
|
|
806
|
+
return `GitHub GraphQL denied access: ${message}`;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return undefined;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function shouldInspectGitHubGraphqlResponse(ctx) {
|
|
813
|
+
if (
|
|
814
|
+
ctx.request.method.toUpperCase() !== "POST" ||
|
|
815
|
+
ctx.response.status !== 200
|
|
816
|
+
) {
|
|
817
|
+
return false;
|
|
818
|
+
}
|
|
819
|
+
let upstreamUrl;
|
|
820
|
+
try {
|
|
821
|
+
upstreamUrl = new URL(ctx.request.url);
|
|
822
|
+
} catch {
|
|
823
|
+
return false;
|
|
824
|
+
}
|
|
825
|
+
if (!isGitHubGraphqlUrl(upstreamUrl)) {
|
|
826
|
+
return false;
|
|
827
|
+
}
|
|
828
|
+
const contentType = ctx.response.headers.get("content-type");
|
|
829
|
+
return contentType ? /\bjson\b/i.test(contentType) : false;
|
|
830
|
+
}
|
|
831
|
+
|
|
718
832
|
function githubApiWriteReason(method, upstreamUrl) {
|
|
719
833
|
const pathname = upstreamUrl.pathname.toLowerCase();
|
|
720
834
|
if (!isGitHubApiUrl(upstreamUrl)) {
|
|
@@ -830,7 +944,11 @@ async function githubGrantForEgress(ctx) {
|
|
|
830
944
|
return grantForAccess("write", writeReason, "user-write");
|
|
831
945
|
}
|
|
832
946
|
|
|
833
|
-
const graphqlAccess = githubGraphqlAccess(
|
|
947
|
+
const graphqlAccess = githubGraphqlAccess(
|
|
948
|
+
method,
|
|
949
|
+
upstreamUrl,
|
|
950
|
+
ctx.request.bodyText,
|
|
951
|
+
);
|
|
834
952
|
if (graphqlAccess) {
|
|
835
953
|
return grantForAccess(
|
|
836
954
|
graphqlAccess,
|
|
@@ -969,6 +1087,21 @@ export function githubPlugin(options = {}) {
|
|
|
969
1087
|
grantForEgress(ctx) {
|
|
970
1088
|
return githubGrantForEgress(ctx);
|
|
971
1089
|
},
|
|
1090
|
+
async onEgressResponse(ctx) {
|
|
1091
|
+
if (!shouldInspectGitHubGraphqlResponse(ctx)) {
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
const bodyText = await ctx.response.readText(
|
|
1095
|
+
GITHUB_GRAPHQL_RESPONSE_BODY_LIMIT_BYTES,
|
|
1096
|
+
);
|
|
1097
|
+
if (!bodyText) {
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
const message = githubGraphqlPermissionDeniedMessage(bodyText);
|
|
1101
|
+
if (message) {
|
|
1102
|
+
ctx.permissionDenied(message);
|
|
1103
|
+
}
|
|
1104
|
+
},
|
|
972
1105
|
async resolveOAuthAccount(ctx) {
|
|
973
1106
|
return await resolveUserAccount(ctx.tokens);
|
|
974
1107
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/junior-github",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.71.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -25,6 +25,6 @@
|
|
|
25
25
|
"SETUP.md"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@sentry/junior-plugin-api": "0.
|
|
28
|
+
"@sentry/junior-plugin-api": "0.71.0"
|
|
29
29
|
}
|
|
30
30
|
}
|
|
@@ -130,15 +130,14 @@ Defaults:
|
|
|
130
130
|
**Footers** (in order):
|
|
131
131
|
|
|
132
132
|
1. Issue references (`Fixes #N`, `Refs SENTRY-N`), if any.
|
|
133
|
-
2.
|
|
134
|
-
3. Session link — when `gen_ai.conversation.id` is available:
|
|
133
|
+
2. Session link — when `gen_ai.conversation.id` is available:
|
|
135
134
|
|
|
136
135
|
```
|
|
137
136
|
---
|
|
138
137
|
[View Session in Sentry](https://sentry.sentry.io/traces/?project=4510944073809921&query=gen_ai.conversation.id%3A%22<url-encoded conversation id>%22)
|
|
139
138
|
```
|
|
140
139
|
|
|
141
|
-
**Assignment:** resolve GitHub handles from evidence (`gh api search/users`, org membership, repo history) before assigning reviewers or
|
|
140
|
+
**Assignment:** resolve GitHub handles from evidence (`gh api search/users`, org membership, repo history) before assigning requested reviewers or assignees. Skip assignment when the handle cannot be confirmed.
|
|
142
141
|
|
|
143
142
|
### 7. Report result
|
|
144
143
|
|
|
@@ -9,7 +9,8 @@ Use this table to recover quickly while keeping operations deterministic.
|
|
|
9
9
|
| `Missing required option --repo` | Repo not passed and no default was resolved. | Resolve with `jr-rpc config get github.repo`; pass `--repo owner/repo` explicitly when missing. |
|
|
10
10
|
| Command affects or authenticates against the wrong repo | Stale `github.repo` default or authenticated command missing explicit repo. | Pass `--repo owner/repo` for the target repository, or update `github.repo` before retrying. |
|
|
11
11
|
| `GraphQL: Could not resolve to a Repository` | Repo slug is wrong or inaccessible. | Validate `owner/repo` and confirm app installation on target repository. |
|
|
12
|
-
| 401 Unauthorized |
|
|
12
|
+
| 401 Unauthorized | Issued GitHub credentials were rejected upstream. | Verify the target repo, then use the grant/auth signal to distinguish stale user OAuth from app installation or host env setup. |
|
|
13
|
+
| `junior-auth-required provider=github grant=user-write` | User-to-server OAuth is missing or stale for a write request. | Follow the private OAuth prompt; do not ask the user to paste or manage tokens manually. |
|
|
13
14
|
| `git push` fails with 401/403 or auth/permission output | Write permission is missing, app installation is too narrow, or remote is wrong. | Verify the remote and repo context, retry once, then confirm app permissions and installation scope if it still fails. |
|
|
14
15
|
| Bash result includes `permission_denied` with `source: "upstream"` | GitHub returned 403 after Junior injected the named grant. | Do not call this a Junior runtime block. Use the message, connected account, upstream target, grant requirements, accepted-permissions, and SSO fields to explain the GitHub denial. |
|
|
15
16
|
| 403 Forbidden | App lacks required permission on repo or install scope is too narrow. | Verify the repo context, then confirm GitHub App permissions and installation scope. |
|
|
@@ -22,7 +23,7 @@ Use this table to recover quickly while keeping operations deterministic.
|
|
|
22
23
|
|
|
23
24
|
- Retry once for transient transport failures after verifying repo context.
|
|
24
25
|
- Do not loop retries on repeated 401/403/404 validation errors.
|
|
25
|
-
-
|
|
26
|
+
- Treat missing or stale `user-read`/`user-write` grants as private GitHub App OAuth work; treat `installation-read` failures as app installation or host environment setup.
|
|
26
27
|
- Do not describe `permission_denied` with `source: "upstream"` as Junior blocking the request. It means the egress proxy injected a credential, forwarded the request, and recorded GitHub's upstream 403. Prefer its `account` and `grant.requirements` fields over inference when explaining what to fix.
|
|
27
28
|
- Do not infer permission level from OAuth scopes. GitHub App user tokens report no OAuth scopes; GitHub App permissions and accepted-permissions headers are the useful evidence.
|
|
28
29
|
- For persistent permission problems, return explicit remediation and stop.
|
|
@@ -59,11 +59,11 @@ Follow [references/research-rules.md](references/research-rules.md) for cross-ty
|
|
|
59
59
|
- Compress source material. Research notes, hypotheses, or transcripts become a short summary + scoped bullets — never paste raw investigation into the body.
|
|
60
60
|
- Do not add desired outcome, expected behavior, or acceptance criteria unless the thread explicitly requests them.
|
|
61
61
|
- Preserve material source references inline.
|
|
62
|
-
- When the request originated from a Slack thread or any on-behalf-of context, append a final line `Action taken on behalf of <name>.` using the action requester's real name. The action requester is the current `<requester>` or the person who explicitly asked you to create/update the issue, not necessarily the original reporter.
|
|
63
62
|
|
|
64
|
-
**
|
|
63
|
+
**Source attribution:**
|
|
65
64
|
|
|
66
|
-
-
|
|
65
|
+
- GitHub records the issue creator natively; do not add body or footer text to identify who asked Junior to create the issue.
|
|
66
|
+
- If the person who originally reported or observed the problem differs from the issue creator, capture that with durable body text such as `Reported by Alice.` or `Raised by Alice during incident triage.`
|
|
67
67
|
- Attach screenshots from the thread as image links when present.
|
|
68
68
|
- Include code snippets, related issues, and related PRs only when they materially improve the issue.
|
|
69
69
|
|
|
@@ -72,7 +72,6 @@ Follow [references/research-rules.md](references/research-rules.md) for cross-ty
|
|
|
72
72
|
Before running the `gh` create/edit command, check each gate. If any fails, revise and re-check before executing:
|
|
73
73
|
|
|
74
74
|
- Title length ≤ 60 characters.
|
|
75
|
-
- Delegated-action footer is the last line when applicable, using the action requester's real name, not the reporter's name unless they are the same person.
|
|
76
75
|
- No session framing remains (channel refs, slash commands, @mentions, Slack thread IDs).
|
|
77
76
|
- Body structure matches complexity — no empty sections, no restated title, no raw research dump.
|
|
78
77
|
|
|
@@ -45,8 +45,6 @@ Good structure — problem-specific sections:
|
|
|
45
45
|
> ## Workaround
|
|
46
46
|
>
|
|
47
47
|
> Retry wrapper that catches LockError and clears the dedup key (PR #32).
|
|
48
|
-
>
|
|
49
|
-
> Action taken on behalf of Jane Doe.
|
|
50
48
|
|
|
51
49
|
## Task example
|
|
52
50
|
|
|
@@ -66,28 +64,6 @@ Good scope — quantified and specific:
|
|
|
66
64
|
> | `processReaction` | scheduling only |
|
|
67
65
|
> | `processAction` | scheduling only |
|
|
68
66
|
> | `processMessage` | scheduling + thread ID normalization + lock retry |
|
|
69
|
-
>
|
|
70
|
-
> Action taken on behalf of Jane Doe.
|
|
71
|
-
|
|
72
|
-
## Distinct reporter/requester example
|
|
73
|
-
|
|
74
|
-
Bad attribution:
|
|
75
|
-
|
|
76
|
-
> The bot resolved the review thread even though the warning still applies.
|
|
77
|
-
>
|
|
78
|
-
> Action taken on behalf of Bojan Oro.
|
|
79
|
-
|
|
80
|
-
Good attribution:
|
|
81
|
-
|
|
82
|
-
> Warden can resolve its own review thread even when the underlying warning still appears valid and the PR remains blocked.
|
|
83
|
-
>
|
|
84
|
-
> Reported by Bojan Oro.
|
|
85
|
-
>
|
|
86
|
-
> - Observed on a PR where Warden left a review comment about a missing backport
|
|
87
|
-
> - The review thread was later marked resolved by the bot
|
|
88
|
-
> - The PR still showed a blocking warning
|
|
89
|
-
>
|
|
90
|
-
> Action taken on behalf of David Cramer.
|
|
91
67
|
|
|
92
68
|
## Feature example
|
|
93
69
|
|
|
@@ -111,8 +87,6 @@ Good framing — current state, gap, options:
|
|
|
111
87
|
> | --------------------------- | ---------------------------------- |
|
|
112
88
|
> | File watch + hot reload | Simple, but no atomicity guarantee |
|
|
113
89
|
> | Config service with polling | Consistent, but adds a dependency |
|
|
114
|
-
>
|
|
115
|
-
> Action taken on behalf of Jane Doe.
|
|
116
90
|
|
|
117
91
|
## Principles
|
|
118
92
|
|
|
@@ -132,5 +106,3 @@ Good framing — current state, gap, options:
|
|
|
132
106
|
- Speculative detail mixed into verified facts
|
|
133
107
|
- Dumping a list of URLs without inline context
|
|
134
108
|
- Session-specific content (slash commands, channel references, raw transcript framing, or unrelated user chatter)
|
|
135
|
-
- Conflating reporter and action requester when they differ
|
|
136
|
-
- Missing delegated attribution footer on user-requested issue creation
|
|
@@ -2,21 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
Use this table to recover quickly while keeping operations deterministic.
|
|
4
4
|
|
|
5
|
-
| Symptom | Likely cause | Fix
|
|
6
|
-
| ------------------------------------------------------- | --------------------------------------------------------------------------- |
|
|
7
|
-
| `unknown command "issue"` from `gh` | CLI version too old or wrong binary in the plugin runtime. | Verify `gh --version`; if it is unavailable or too old, report that the GitHub plugin runtime dependency is not available.
|
|
8
|
-
| `Missing required option --repo` | Repo not passed and no default was resolved. | Resolve with `jr-rpc config get github.repo`; pass `--repo owner/repo` explicitly when missing.
|
|
9
|
-
| Command affects or authenticates against the wrong repo | Stale `github.repo` default or authenticated command missing explicit repo. | Pass `--repo owner/repo` for the target repository, or update `github.repo` before retrying.
|
|
10
|
-
| `GraphQL: Could not resolve to a Repository` | Repo slug is wrong or inaccessible. | Validate `owner/repo` and confirm app installation on target repository.
|
|
11
|
-
| 401 Unauthorized |
|
|
12
|
-
|
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
|
|
|
5
|
+
| Symptom | Likely cause | Fix |
|
|
6
|
+
| ------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
7
|
+
| `unknown command "issue"` from `gh` | CLI version too old or wrong binary in the plugin runtime. | Verify `gh --version`; if it is unavailable or too old, report that the GitHub plugin runtime dependency is not available. |
|
|
8
|
+
| `Missing required option --repo` | Repo not passed and no default was resolved. | Resolve with `jr-rpc config get github.repo`; pass `--repo owner/repo` explicitly when missing. |
|
|
9
|
+
| Command affects or authenticates against the wrong repo | Stale `github.repo` default or authenticated command missing explicit repo. | Pass `--repo owner/repo` for the target repository, or update `github.repo` before retrying. |
|
|
10
|
+
| `GraphQL: Could not resolve to a Repository` | Repo slug is wrong or inaccessible. | Validate `owner/repo` and confirm app installation on target repository. |
|
|
11
|
+
| 401 Unauthorized | Issued GitHub credentials were rejected upstream. | Verify the target repo, then use the grant/auth signal to distinguish stale user OAuth from app installation or host env setup. |
|
|
12
|
+
| `junior-auth-required provider=github grant=user-write` | User-to-server OAuth is missing or stale for a write request. | Follow the private OAuth prompt; do not ask the user to paste or manage tokens manually. |
|
|
13
|
+
| 403 Forbidden | App lacks required permission on repo or install scope is too narrow. | Verify the repo context, then confirm GitHub App permissions and installation scope. |
|
|
14
|
+
| 404 Not Found | Issue number or repo is wrong. | Validate repo + issue ID with `gh issue view NUMBER --repo owner/repo`. |
|
|
15
|
+
| `gh issue edit` does not change labels | Wrong flag usage or wrong repo context. | Use repeated `--add-label/--remove-label` flags and keep `--repo owner/repo` explicit. |
|
|
16
|
+
| Comment command fails with empty body | Body file missing/empty. | Ensure comment file exists and has content before `gh issue comment`. |
|
|
16
17
|
|
|
17
18
|
## Retry guidance
|
|
18
19
|
|
|
19
20
|
- Retry once for transient transport failures after verifying repo context.
|
|
20
21
|
- Do not loop retries on repeated 401/403/404 validation errors.
|
|
21
|
-
-
|
|
22
|
+
- Treat missing or stale `user-read`/`user-write` grants as private GitHub App OAuth work; treat `installation-read` failures as app installation or host environment setup.
|
|
22
23
|
- For persistent permission problems, return explicit remediation and stop.
|