diff --git a/src/app/git/components/providers/GithubProviderModal.tsx b/src/app/git/components/providers/GithubProviderModal.tsx
index 36e4c56..112ebd7 100644
--- a/src/app/git/components/providers/GithubProviderModal.tsx
+++ b/src/app/git/components/providers/GithubProviderModal.tsx
@@ -5,7 +5,6 @@ import {
} from "@/app/settings/components/SettingsPrimitives";
import {
type GithubProviderModalProps,
- ProviderBadge,
getProviderMeta,
launchGithubManifestFlow,
} from "./git-provider-shared";
@@ -117,7 +116,19 @@ export default function GithubProviderModal({
placeholder="Organization name"
/>
- ) : null}
+ ) : (
+
+ GitHub Username
+
+ updateDraft("accountUsername", event.target.value)
+ }
+ placeholder="Your GitHub username"
+ />
+
+ )}
updateDraft("appName", event.target.value)}
- placeholder={`Doktainer-${new Date().toISOString().split("T")[0].replace(/-/g, "")}-${Math.random().toString(36).slice(2, 10)}`}
+ placeholder="Doktainer-20260718-ab12cd34"
/>
diff --git a/src/app/git/components/providers/GitlabProviderModal.tsx b/src/app/git/components/providers/GitlabProviderModal.tsx
index edb1a81..fdafd51 100644
--- a/src/app/git/components/providers/GitlabProviderModal.tsx
+++ b/src/app/git/components/providers/GitlabProviderModal.tsx
@@ -2,7 +2,6 @@ import { ExternalLink } from "lucide-react";
import { FieldLabel } from "@/app/settings/components/SettingsPrimitives";
import {
type GitProviderModalBaseProps,
- ProviderBadge,
getProviderMeta,
} from "./git-provider-shared";
import { markGitProviderCallbackIntent } from "@/lib/git-provider-callback-intent";
@@ -193,6 +192,18 @@ export default function GitlabProviderModal({
placeholder="my-org"
/>
+
+
+ GitLab Username (Required Without Group)
+
+ updateDraft("accountUsername", event.target.value)
+ }
+ placeholder="Your GitLab username"
+ />
+
>
);
diff --git a/src/server/routes/git-providers.ts b/src/server/routes/git-providers.ts
index fa9bbc1..4029c4b 100644
--- a/src/server/routes/git-providers.ts
+++ b/src/server/routes/git-providers.ts
@@ -1,7 +1,7 @@
import { FastifyInstance } from "fastify";
import { z } from "zod";
import prisma from "../lib/prisma";
-import { decrypt, encrypt } from "../lib/crypto";
+import { encrypt } from "../lib/crypto";
import { authenticate, requireRole } from "../middleware/auth";
import { auditLog } from "../services/audit.service";
import { verifyGitProviderConfiguration } from "../services/integration-verification.service";
@@ -170,6 +170,18 @@ function getProviderOwner(provider: GitProviderRecord) {
);
}
+function getGithubInstallationTargetId(installationUrl?: string | null) {
+ const rawUrl = toTrimmedValue(installationUrl);
+ if (!rawUrl) return "";
+
+ try {
+ const targetId = new URL(rawUrl).searchParams.get("target_id")?.trim();
+ return targetId && /^\d+$/.test(targetId) ? targetId : "";
+ } catch {
+ return "";
+ }
+}
+
function buildProviderFetchErrorMessage(args: {
provider: GitProviderRecord["provider"];
resource: string;
@@ -247,6 +259,32 @@ async function fetchJson(
return json;
}
+async function resolveProviderOwner(provider: GitProviderRecord) {
+ const configuredOwner = getProviderOwner(provider);
+ if (configuredOwner || provider.provider !== "GITHUB") {
+ return configuredOwner;
+ }
+
+ // GitHub's manifest flow stores the installation target account ID in the
+ // installation URL. Older personal-account records did not persist a
+ // username, so resolve the public login from that stable account ID.
+ const targetId = getGithubInstallationTargetId(provider.installationUrl);
+ if (!targetId) return "";
+
+ const baseUrl = normalizeBaseUrl(provider.providerUrl, "https://github.com");
+ const apiBase =
+ baseUrl.toLowerCase() === "https://github.com"
+ ? "https://api.github.com"
+ : `${baseUrl}/api/v3`;
+ const payload = await fetchJson(`${apiBase}/user/${targetId}`, {
+ provider: provider.provider,
+ resource: "GitHub account",
+ });
+
+ if (!payload || typeof payload !== "object") return "";
+ return toTrimmedValue((payload as { login?: string }).login);
+}
+
function mapGithubRepositories(payload: unknown): GitProviderRepository[] {
if (!Array.isArray(payload)) return [];
@@ -468,7 +506,7 @@ function mapGiteaBranches(
async function listRepositoriesForProvider(
provider: GitProviderRecord,
): Promise {
- const owner = getProviderOwner(provider);
+ const owner = await resolveProviderOwner(provider);
if (!owner) {
throw new Error(
"Configure namespace, organization name, or account username on the Git provider before loading repositories",