Skip to content

feat: GitHub OAuth login with auto-linking and access control#121

Merged
coji merged 4 commits intomainfrom
feat/github-oauth-login
Feb 27, 2026
Merged

feat: GitHub OAuth login with auto-linking and access control#121
coji merged 4 commits intomainfrom
feat/github-oauth-login

Conversation

@coji
Copy link
Owner

@coji coji commented Feb 27, 2026

Summary

  • GitHub OAuth ログインを追加(Better Auth のカスタム getUserInfo で安定動作)
  • ログイン時に companyGithubUsers.login で自動紐付け(session hook で毎回実行、冪等)
  • companyGithubUsers に登録済みの GitHub ユーザーのみログイン許可(全 org の tenant DB を検索)
  • 未許可ユーザーにはログイン画面でエラーメッセージを表示
  • 同一メールの Google/GitHub アカウントは account linking で自動統合
  • 前回使ったログインプロバイダのボタンに脈打つ青いビーコンを表示

Test plan

  • companyGithubUsers に未登録の GitHub アカウントでログイン → エラーメッセージが表示される
  • 設定画面で GitHub login を追加後、GitHub でログイン → ログイン成功
  • companyGithubUsers.userId が自動で紐付けされることを確認
  • Google でログイン → ログアウト → ログイン画面で Google ボタンにビーコン表示
  • GitHub でログイン → ログアウト → ログイン画面で GitHub ボタンにビーコン表示
  • 同じメールの Google/GitHub アカウント → 同一ユーザーに統合される
  • pnpm typecheck が通ること

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added GitHub as a login option alongside Google.
    • Login page now shows provider-specific errors and remembers the last-used provider.
    • GitHub accounts are automatically linked to company records after sign-in when applicable.
  • Documentation

    • Docs updated with GitHub login integration, linking behavior, and setup guidance.
    • README expanded with full setup and authentication instructions.
  • Chores

    • Example environment file now documents optional GitHub OAuth variables.

coji and others added 2 commits February 27, 2026 23:22
- Add GitHub as a social login provider via Better Auth with custom getUserInfo
- Auto-link GitHub users to companyGithubUsers on each login via session hook
- Support account linking (same email merges Google + GitHub accounts)
- Show pulsing blue beacon on login button for last-used provider
- Update auth docs with correct account_id (numeric ID) behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… error on login page

- Check all org tenant DBs for GitHub login in companyGithubUsers before allowing sign-in
- Redirect OAuth errors to /login with error query param
- Display user-friendly error message on login page
- Add errorCallbackURL to signInSocial call

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2026

Warning

Rate limit exceeded

@coji has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 8 minutes and 14 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between df3436c and f04a416.

📒 Files selected for processing (3)
  • app/libs/auth.server.ts
  • app/services/github-linking.server.ts
  • docs/auth-and-invitation.md
📝 Walkthrough

Walkthrough

Adds GitHub OAuth as a social provider, implements server-side GitHub profile/email resolution and cross-tenant company linking, updates the login UI to support GitHub sign-in and error handling, and documents the updated GitHub linking behavior.

Changes

Cohort / File(s) Summary
Environment & Config
\.env.example, vite.config.ts
Added GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET examples; adjusted tsconfigPaths in Vite to tsconfigPaths({ ignoreConfigErrors: true }).
Auth Library / Config
app/libs/auth.server.ts
Added github social provider with getUserInfo(token) (profile + email resolution + org membership checks), added accountLinking.trustedProviders = ['google','github'], and a session.create.after hook calling linkGithubUserToCompanyUsers.
GitHub Linking Service
app/services/github-linking.server.ts
New linkGithubUserToCompanyUsers(userId) that fetches the user's GitHub token/profile and updates companyGithubUsers across tenant DBs for all memberships where login matches.
Login UI
app/routes/_auth/login.tsx
Reworked loader/action and client flow: provider selection (Google/GitHub), last-provider cookie, error mapping/UI components, and button-driven sign-in with errorCallback handling.
Docs & Guidance
docs/auth-and-invitation.md, README.md, CLAUDE.md
Doc updates to use GitHub profile.login for matching, clarified automatic vs manual linking and Better Auth snippets; README expanded.
Misc
app/libs/..., other minor files
Added tenant DB utilities and reserved-slugs/lib references in docs; small exports/import adjustments to support GitHub linking.

Sequence Diagram

sequenceDiagram
    participant User as User/Browser
    participant LoginUI as Login Page
    participant AuthServer as Auth Server
    participant GithubAPI as GitHub API
    participant TenantDB as Tenant DB
    participant SessionHook as Session Hook

    User->>LoginUI: Click "Login with GitHub"
    LoginUI->>AuthServer: POST provider='github' + errorCallbackURL
    AuthServer->>GithubAPI: GET /user (with access token)
    GithubAPI-->>AuthServer: User profile {id, login, email}
    AuthServer->>TenantDB: Query orgs / companyGithubUsers by profile.login
    TenantDB-->>AuthServer: Membership/match result
    AuthServer->>AuthServer: Create session
    AuthServer->>SessionHook: Trigger session.create.after
    SessionHook->>GithubAPI: GET /user (resolve login/email)
    SessionHook->>TenantDB: UPDATE companyGithubUsers SET userId WHERE login=profile.login
    TenantDB-->>SessionHook: Update results
    SessionHook-->>AuthServer: Linking complete
    AuthServer-->>LoginUI: Redirect with session
    LoginUI-->>User: Authenticated
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 I hopped through code to add a GitHub trail,
I fetched logins, emails, and followed each mail.
Across tenant fields the links now bind,
A hopping hooray from this rabbit behind! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: GitHub OAuth login with auto-linking and access control' clearly and concisely summarizes the main change: adding GitHub OAuth with automatic user linking and access control.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/github-oauth-login

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (1)
app/libs/auth.server.ts (1)

174-176: Don’t silently swallow post-session linking failures.

Line [175] drops all errors with .catch(() => {}), which removes operational visibility when linking breaks. At minimum, log a structured warning with session.userId.

🔍 Proposed improvement
-          await linkGithubUserToCompanyUsers(session.userId).catch(() => {})
+          await linkGithubUserToCompanyUsers(session.userId).catch((error) => {
+            console.warn('[GitHub linking] post-session linking failed', {
+              userId: session.userId,
+              error,
+            })
+          })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/libs/auth.server.ts` around lines 174 - 176, The post-session hook is
currently swallowing all errors from linkGithubUserToCompanyUsers via .catch(()
=> {}); change this to catch and log a structured warning that includes the
session.userId and the error details (e.g., in the after: async (session) =>
handler, call linkGithubUserToCompanyUsers(session.userId).catch(err => /* log
warn with session.userId and err */)). Use the project's logger (or console.warn
if none) so failures are visible and include a clear message, session.userId,
and the caught error.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/libs/auth.server.ts`:
- Around line 58-71: The current orgs.map + Promise.all spawns an unbounded
query per org causing FD/IO spikes; replace it with a bounded-concurrency or
early-exit loop: either iterate orgs with a for..of and await each tenant check
(calling getTenantDb(...) and the
selectFrom('companyGithubUsers').select(['login']).where('login','=',profile.login').executeTakeFirst())
returning immediately when a match is found, or use a concurrency limiter (e.g.,
p-limit) to run N tenantDb checks in parallel and stop further scheduling when a
match is discovered; update the code around the orgs.map usage to use
getTenantDb and the selectFrom query inside the bounded/early-exit approach.
- Around line 30-35: The GitHub fetches in getUserInfo currently have no
timeouts; wrap both external calls (the fetch to 'https://api.github.com/user'
and the other GitHub API call inside getUserInfo) with an AbortController-based
timeout (create AbortController, pass controller.signal to fetch, set a
setTimeout to call controller.abort() after a reasonable timeout like 3–10s, and
clearTimeout on success), and handle the abort error path so getUserInfo returns
or throws a clear timeout error; update both fetch invocations and their error
handling to use the same pattern so upstream stalls cannot hang the auth flow.
- Around line 23-24: The GitHub provider is being force-cast to string
(process.env.GITHUB_CLIENT_ID as string / process.env.GITHUB_CLIENT_SECRET as
string), which hides missing env vars; update the provider registration in
auth.server.ts to mirror the Google pattern by only adding the GitHub provider
when both process.env.GITHUB_CLIENT_ID and process.env.GITHUB_CLIENT_SECRET are
present, remove the `as string` casts, and conditionally push or spread the
GitHub provider into the providers array used by the function that builds the
auth options (e.g., where the Google provider is registered) so provider setup
fails fast if envs are missing.
- Around line 63-66: The current query against companyGithubUsers uses
exact-case matching (.where('login', '=', profile.login) in the
selectFrom('companyGithubUsers') block), which breaks because GitHub logins are
case-insensitive; update the codebase to normalize and compare logins
case-insensitively: ensure addGithubUser and updateGithubUser in
mutations.server.ts store logins as lowercase, and change lookups (the select in
auth.server.ts and the query in github-linking.server.ts) to compare lowercase
values (e.g., compare lower(login) to profile.login.toLowerCase or use a
case-insensitive DB operator) so matching works regardless of stored or returned
casing.

In `@app/routes/_auth/login.tsx`:
- Around line 41-43: Replace the raw FormData cast for provider with Conform +
Zod validation: define a Zod schema (e.g., providerSchema or part of the
existing form schema) that enforces provider as z.enum(['google','github']),
then call parseWithZod (the same helper used across routes) against
request.formData() and extract the validated provider value instead of using
String(formData.get(...)). Update any local references in this file (the
provider const and related logic that uses it) to use the validated value
returned by parseWithZod.

In `@app/services/github-linking.server.ts`:
- Around line 26-32: The GitHub fetch call (the
fetch('https://api.github.com/user', { Authorization: `Bearer
${githubAccount.accessToken}` })) needs a client-side timeout so a
slow/unresponsive GitHub API doesn't block the post-login hook; wrap the fetch
with an AbortController, pass controller.signal to fetch, schedule a setTimeout
that calls controller.abort() after a short timeout (e.g., 3–5s), and clear that
timer after the fetch completes; ensure the existing try/catch still handles the
abort error, and apply this change around the fetch invocation that produces res
so the request is aborted on timeout instead of indefinitely awaiting a
response.

In `@docs/auth-and-invitation.md`:
- Around line 85-90: The docs snippet under socialProviders (github.clientId /
github.clientSecret / mapProfileToUser) uses GITHUB_APP_CLIENT_ID and
GITHUB_APP_CLIENT_SECRET but the runtime auth config reads GITHUB_CLIENT_ID and
GITHUB_CLIENT_SECRET; update the documented env var names to match the runtime
(change GITHUB_APP_CLIENT_ID -> GITHUB_CLIENT_ID and GITHUB_APP_CLIENT_SECRET ->
GITHUB_CLIENT_SECRET) or explicitly note both variants and the expected one, so
the socialProviders.clientId/clientSecret examples align with the values read by
the auth config.

---

Nitpick comments:
In `@app/libs/auth.server.ts`:
- Around line 174-176: The post-session hook is currently swallowing all errors
from linkGithubUserToCompanyUsers via .catch(() => {}); change this to catch and
log a structured warning that includes the session.userId and the error details
(e.g., in the after: async (session) => handler, call
linkGithubUserToCompanyUsers(session.userId).catch(err => /* log warn with
session.userId and err */)). Use the project's logger (or console.warn if none)
so failures are visible and include a clear message, session.userId, and the
caught error.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9dcaf0f and ff54172.

📒 Files selected for processing (6)
  • .env.example
  • app/libs/auth.server.ts
  • app/routes/_auth/login.tsx
  • app/services/github-linking.server.ts
  • docs/auth-and-invitation.md
  • vite.config.ts

Comment on lines +23 to +24
clientId: process.env.GITHUB_CLIENT_ID as string,
clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
Copy link

@coderabbitai coderabbitai bot Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find the file and read the relevant section
fd -t f "auth.server.ts" app/

Repository: coji/upflow

Length of output: 78


🏁 Script executed:

# Read the auth.server.ts file with context around lines 23-24
wc -l app/libs/auth.server.ts

Repository: coji/upflow

Length of output: 82


🏁 Script executed:

# Read the file to see the exact code
cat -n app/libs/auth.server.ts | head -100

Repository: coji/upflow

Length of output: 4052


🏁 Script executed:

# Continue reading the file to see the rest of the implementation
cat -n app/libs/auth.server.ts | sed -n '100,200p'

Repository: coji/upflow

Length of output: 3080


🏁 Script executed:

# Check for any env var validation or initialization
cat -n app/libs/auth.server.ts | sed -n '200,384p'

Repository: coji/upflow

Length of output: 6154


🏁 Script executed:

# Search for usage of socialProviders or auth config elsewhere in the codebase
rg "socialProviders|betterAuth\(" --type ts --type tsx -B 2 -A 2

Repository: coji/upflow

Length of output: 82


🏁 Script executed:

# Check how the auth object is exported and used
rg "export.*auth|from.*auth\.server" --type ts --type tsx -A 2

Repository: coji/upflow

Length of output: 82


🏁 Script executed:

# Search for usage of auth or socialProviders
rg "auth\." --max-count 20

Repository: coji/upflow

Length of output: 3582


🏁 Script executed:

# Check how the auth object is used in routes or handlers
rg "from.*auth\.server|import.*auth" --max-count 20

Repository: coji/upflow

Length of output: 4745


🏁 Script executed:

# Look for environment variable configuration/validation
rg "GITHUB_CLIENT_ID|GITHUB_CLIENT_SECRET"

Repository: coji/upflow

Length of output: 222


🏁 Script executed:

# Check betterAuth documentation or error handling around provider config
rg "betterAuth" -A 5 -B 5

Repository: coji/upflow

Length of output: 1038


🏁 Script executed:

# Check if there are environment variable setup/docs
fd -t f "\.env|README|SETUP" | head -20

Repository: coji/upflow

Length of output: 94


🏁 Script executed:

# Verify the login flow that uses the auth object
cat -n app/routes/_auth/login.tsx | head -60

Repository: coji/upflow

Length of output: 2032


Don't force-cast optional GitHub env vars to string.

Lines 23-24 mask missing configuration at compile-time and can leave runtime provider setup broken. Gate GitHub provider registration on actual env presence, following the pattern used for the Google provider (lines 18-20).

🔧 Proposed fix
-    github: {
-      clientId: process.env.GITHUB_CLIENT_ID as string,
-      clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
-      getUserInfo: async (token) => {
+    ...(process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET
+      ? {
+          github: {
+            clientId: process.env.GITHUB_CLIENT_ID,
+            clientSecret: process.env.GITHUB_CLIENT_SECRET,
+            getUserInfo: async (token) => {
               // existing implementation...
-      },
-    },
+            },
+          },
+        }
+      : {}),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/libs/auth.server.ts` around lines 23 - 24, The GitHub provider is being
force-cast to string (process.env.GITHUB_CLIENT_ID as string /
process.env.GITHUB_CLIENT_SECRET as string), which hides missing env vars;
update the provider registration in auth.server.ts to mirror the Google pattern
by only adding the GitHub provider when both process.env.GITHUB_CLIENT_ID and
process.env.GITHUB_CLIENT_SECRET are present, remove the `as string` casts, and
conditionally push or spread the GitHub provider into the providers array used
by the function that builds the auth options (e.g., where the Google provider is
registered) so provider setup fails fast if envs are missing.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

意図的に必須化しました。GitHub OAuth はこのプロジェクトでは必須設定なので、条件分岐は不要です。env 未設定時はアプリ起動時に better-auth 側でエラーになります。

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coji, 了解しました!GitHub OAuth を必須とする設計判断ですね。better-auth が起動時にエラーを出してくれるので、環境変数の欠落は早期に検知できますね。ご説明ありがとうございます。

Comment on lines +41 to +43
const formData = await request.formData()
const provider = String(formData.get('provider') || 'google')

Copy link

@coderabbitai coderabbitai bot Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n app/routes/_auth/login.tsx | head -80

Repository: coji/upflow

Length of output: 2819


🏁 Script executed:

# Check if conform is installed and what version
cat package.json | grep -i conform

Repository: coji/upflow

Length of output: 122


🏁 Script executed:

# Search for parseWithZod usage in the codebase to verify this pattern
rg "parseWithZod" app/routes --type ts --type tsx -A 3

Repository: coji/upflow

Length of output: 82


🏁 Script executed:

# Search for parseWithZod usage in routes
rg "parseWithZod" app/routes -A 3

Repository: coji/upflow

Length of output: 9080


🏁 Script executed:

# Check for other provider values in auth configuration
rg "provider" app/libs/auth.server -i

Repository: coji/upflow

Length of output: 115


🏁 Script executed:

# Find auth.server file
find . -name "*auth*server*" -type f

Repository: coji/upflow

Length of output: 80


🏁 Script executed:

# Check for auth import and provider handling
rg "auth.api.signInSocial" -A 5 -B 5

Repository: coji/upflow

Length of output: 634


🏁 Script executed:

# Check auth.server implementation
cat -n app/libs/auth.server.ts | head -150

Repository: coji/upflow

Length of output: 5520


Validate provider with Conform + Zod to align with route validation pattern.

Line 42 accepts any string via raw FormData casting, bypassing schema validation. All other routes in the codebase use parseWithZod for form validation. Please enforce provider as an enum (google | github).

Example refactor
+import { parseWithZod } from '@conform-to/zod/v4'
+import { z } from 'zod'
...
   const formData = await request.formData()
-  const provider = String(formData.get('provider') || 'google')
+  const submission = parseWithZod(formData, {
+    schema: z.object({
+      provider: z.enum(['google', 'github']).default('google'),
+    }),
+  })
+  if (submission.status !== 'success') {
+    throw new Response('Invalid provider', { status: 400 })
+  }
+  const { provider } = submission.value
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/routes/_auth/login.tsx` around lines 41 - 43, Replace the raw FormData
cast for provider with Conform + Zod validation: define a Zod schema (e.g.,
providerSchema or part of the existing form schema) that enforces provider as
z.enum(['google','github']), then call parseWithZod (the same helper used across
routes) against request.formData() and extract the validated provider value
instead of using String(formData.get(...)). Update any local references in this
file (the provider const and related logic that uses it) to use the validated
value returned by parseWithZod.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ログインページの action はユーザーが直接操作するボタン経由でのみ呼ばれ、provider 値は固定文字列 ('google' / 'github') です。Conform + Zod でのバリデーションは over-engineering と判断し、対応しません。

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coji, understood! I appreciate your reasoning. While the validation would provide defense-in-depth against unexpected requests, I agree that in this case—with button-driven submissions using hardcoded strings—the risk is minimal and the added complexity may not be warranted.

Thank you for the explanation. I'll note your decision.


🧠 Learnings used
Learnt from: CR
Repo: coji/upflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-26T06:54:40.352Z
Learning: Applies to app/routes/$orgSlug/**/*.{ts,tsx} : For org-scoped routes, call `requireOrgMember` or `requireOrgAdmin` authentication guards BEFORE `parseWithZod(request.formData())` to prevent unauthenticated users from receiving validation errors

Learnt from: CR
Repo: coji/upflow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-26T06:54:40.352Z
Learning: Applies to app/routes/**/*.{ts,tsx} : Use Conform with Zod for type-safe form validation with `parseWithZod` from 'conform-to/zod/v4'

coji and others added 2 commits February 28, 2026 00:02
…info

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add 5s fetch timeouts (AbortSignal.timeout) to all GitHub API calls
- Replace Promise.all with for-of early exit for org allowlist check
- Normalize GitHub login comparison to case-insensitive (lower())
- Log errors in post-session GitHub linking catch handler
- Fix env var names in docs (GITHUB_APP_CLIENT_* → GITHUB_CLIENT_*)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coji coji merged commit 41c0a34 into main Feb 27, 2026
6 checks passed
@coji coji deleted the feat/github-oauth-login branch February 27, 2026 15:16
@coji
Copy link
Owner Author

coji commented Feb 27, 2026

CodeRabbit review feedback addressed in f04a416

# Issue Resolution
1 as string cast on GitHub env vars Won't fix — intentionally required, no conditional needed
2 GitHub API fetch timeout FixedAbortSignal.timeout(5_000) on both /user and /user/emails
3 Unbounded Promise.all over orgs Fixedfor...of with early exit on first match
4 Case-insensitive login matching Fixedlower() in SQL + .toLowerCase() in JS
5 Conform + Zod for provider Won't fix — over-engineering for hardcoded button values
6 Fetch timeout in github-linking FixedAbortSignal.timeout(5_000)
7 Env var names in docs FixedGITHUB_APP_CLIENT_*GITHUB_CLIENT_*
nitpick Silent .catch(() => {}) Fixed — now logs with console.warn including userId and error

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant