@sentry/junior-github 0.194.0 → 0.195.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 CHANGED
@@ -156,7 +156,7 @@ Use `additionalUserScopes` only when a human-identity integration flow requires
156
156
  - The plugin classifies GitHub traffic from the forwarded HTTP request. Reads use `installation-read`, while `GET /user` uses `user-read`. Allowlisted App-owned mutations and Git smart-HTTP pushes use `installation-write`. User-attachment uploads to `uploads.github.com/user-attachments/assets` use `user-write`. Unknown REST writes and GraphQL mutations are denied.
157
157
  - `user-read` and explicitly human `user-write` operations require the actor, or an explicitly delegated user subject, to authorize the GitHub App through the private OAuth flow. Junior-owned issue, pull request, review, inline review comment, and branch operations do not fall back to user OAuth.
158
158
  - Headless resource-event turns use the `resource-event` system actor and may receive the same installation grants. This lets Junior respond to subscribed pull request events by committing and pushing fixes without inheriting a subscriber's OAuth credential.
159
- - Git commits use Junior as author and committer. Resolvable human run actors are credited once with `Co-Authored-By` trailers.
159
+ - Git commits use Junior as author and committer. Resolvable human run actors are credited once with `Co-Authored-By` trailers. When the current actor has a linked GitHub identity, Junior prefers a GitHub noreply address so external automation can resolve a login for assignment.
160
160
  - Installation credential leases are cached on the host by grant name and reused across sandboxes until near expiry. User grants stay actor-scoped. Upstream 403 after injection clears the cached lease, issues a new token, and retries the hop once before recording permission denied.
161
161
  - Sandbox does not receive raw tokens via env; host applies Authorization header transforms for GitHub API and upload calls.
162
162
 
@@ -187,7 +187,7 @@ The plugin uses installation credentials for read-only GitHub traffic, workflow
187
187
 
188
188
  Committing and pushing code uses more than one GitHub surface:
189
189
 
190
- - Creating the local Git commit does not call GitHub. Junior sets the GitHub App bot as author and committer and credits resolvable human actors with `Co-Authored-By` trailers.
190
+ - Creating the local Git commit does not call GitHub. Junior sets the GitHub App bot as author and committer and credits resolvable human actors with `Co-Authored-By` trailers, preferring a linked GitHub noreply email for the current actor when present.
191
191
  - Pushing a branch with Git smart HTTP (`git push`) uses the `installation-write` grant and requires the App installation to have `Contents: write`. Workflow-file changes also require the installation to have `Workflows: write`.
192
192
  - The smart-HTTP classifier does not distinguish Junior-managed branches or independently detect force updates or ref deletion. Use GitHub branch protection and limit the App installation to repositories where Junior may push.
193
193
  - REST Git database and ref writes are denied by the current write allowlist. Use Git smart HTTP (`git push`) for branch updates instead.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Git identity and commit-hook setup for GitHub sandboxes.
3
3
  */
4
- import type { Actor, SandboxPrepareHookContext } from "@sentry/junior-plugin-api";
4
+ import type { Actor, SandboxPrepareHookContext, User } from "@sentry/junior-plugin-api";
5
5
  /**
6
6
  * Build `Co-Authored-By` trailers crediting human run actors.
7
7
  *
@@ -10,10 +10,15 @@ import type { Actor, SandboxPrepareHookContext } from "@sentry/junior-plugin-api
10
10
  * denying the commit. Dedupes by identity and resolved email so the same human
11
11
  * under two display profiles, or an actor matching the bot identity, only ever
12
12
  * produces one line.
13
+ *
14
+ * When the current actor has a linked GitHub identity, prefer that noreply
15
+ * address so external automation can resolve a login for assignment.
13
16
  */
14
17
  export declare function additionalActorCoauthorTrailers(args: {
15
18
  actors?: Actor[];
16
19
  botEmail: string;
20
+ /** Linked user for the current run actor, when already resolved. */
21
+ currentUser?: User;
17
22
  }): string[];
18
23
  /** Build the hook that replaces model-supplied commit attribution. */
19
24
  export declare function prepareCommitMsgHook(): string;
package/dist/index.js CHANGED
@@ -3201,6 +3201,23 @@ function actorEmail(actor) {
3201
3201
  const email = cleanIdentityPart(actor?.email);
3202
3202
  return /^[^\s@<>]+@[^\s@<>]+\.[^\s@<>]+$/.test(email) ? email : void 0;
3203
3203
  }
3204
+ function githubNoreplyEmail(user) {
3205
+ for (const identity of user?.identities ?? []) {
3206
+ if (identity.provider !== "github") {
3207
+ continue;
3208
+ }
3209
+ const login = cleanIdentityPart(identity.handle);
3210
+ const userId = cleanIdentityPart(identity.providerSubjectId);
3211
+ if (!login || login.toLowerCase().endsWith("[bot]")) {
3212
+ continue;
3213
+ }
3214
+ if (/^[1-9]\d*$/.test(userId)) {
3215
+ return `${userId}+${login}@users.noreply.github.com`;
3216
+ }
3217
+ return `${login}@users.noreply.github.com`;
3218
+ }
3219
+ return void 0;
3220
+ }
3204
3221
  function actorIdentityKey(actor) {
3205
3222
  if (actor.platform === "system") {
3206
3223
  return `system ${actor.name}`;
@@ -3211,6 +3228,8 @@ function additionalActorCoauthorTrailers(args) {
3211
3228
  if (!args.actors || args.actors.length === 0) {
3212
3229
  return [];
3213
3230
  }
3231
+ const currentActorKey = args.actors[0] ? actorIdentityKey(args.actors[0]) : void 0;
3232
+ const currentNoreply = githubNoreplyEmail(args.currentUser);
3214
3233
  const seenEmails = /* @__PURE__ */ new Set([args.botEmail.toLowerCase()]);
3215
3234
  const seenActors = /* @__PURE__ */ new Set();
3216
3235
  const trailers = [];
@@ -3220,7 +3239,7 @@ function additionalActorCoauthorTrailers(args) {
3220
3239
  continue;
3221
3240
  }
3222
3241
  const name = actorName(candidate);
3223
- const email = actorEmail(candidate);
3242
+ const email = (currentActorKey === actorKey ? currentNoreply : void 0) || actorEmail(candidate);
3224
3243
  if (!name || !email) {
3225
3244
  continue;
3226
3245
  }
@@ -4350,7 +4369,7 @@ function githubPlugin(options = {}) {
4350
4369
  await configureGit(ctx, "credential.helper", "");
4351
4370
  await configureGit(ctx, "http.emptyAuth", "true");
4352
4371
  },
4353
- beforeToolExecute(ctx) {
4372
+ async beforeToolExecute(ctx) {
4354
4373
  if (ctx.tool.name !== "bash") {
4355
4374
  return;
4356
4375
  }
@@ -4362,9 +4381,18 @@ function githubPlugin(options = {}) {
4362
4381
  ctx.env.set("JUNIOR_GIT_AUTHOR_EMAIL", botEmail);
4363
4382
  ctx.env.set("GIT_COMMITTER_NAME", botName);
4364
4383
  ctx.env.set("GIT_COMMITTER_EMAIL", botEmail);
4384
+ let currentUser;
4385
+ if (ctx.actor) {
4386
+ try {
4387
+ currentUser = (await ctx.users.resolveActor())?.user;
4388
+ } catch {
4389
+ currentUser = void 0;
4390
+ }
4391
+ }
4365
4392
  const actorTrailers = additionalActorCoauthorTrailers({
4366
4393
  actors: [...ctx.actor ? [ctx.actor] : [], ...ctx.actors ?? []],
4367
- botEmail
4394
+ botEmail,
4395
+ currentUser
4368
4396
  });
4369
4397
  ctx.env.set(
4370
4398
  "JUNIOR_GIT_ACTOR_COAUTHOR_TRAILERS",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-github",
3
- "version": "0.194.0",
3
+ "version": "0.195.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.194.0"
34
+ "@sentry/junior-plugin-api": "0.195.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@oxlint/plugins": "1.79.0",