c86752cf1d
* add wiki/betterstack.md documenting log querying, request-ID grouping, and MCP usage
Made-with: Cursor
* fix webhook race conditions: separate runId assignment from data updates
the workflow_run webhook handler had a race where concurrent handlers assigned
the same runId to different pending records. the loser's P2002 silently dropped
data updates (jobId, status, completedAt). fix by splitting into two steps:
assignRunId() handles the race-safe unique assignment, then data updates always
target where: { runId } so they hit the correct record regardless of who won.
also downgrade R2 ObjectLockedByBucketPolicy errors from error to warn level
since duplicate webhook deliveries writing the same key is expected under load.
Made-with: Cursor
* require OIDC verification for DB secrets on run-context endpoint
DB secrets transported via run-context were accessible to any GitHub API
token with read access, bypassing GitHub Actions' fork PR secret isolation.
Now the endpoint requires a valid GitHub Actions OIDC token
(X-GitHub-OIDC-Token header) with a matching repository claim before
returning dbSecrets. Also requires admin for account-scope CLI secret
writes (matching the dashboard), and removes dead redactSecrets code.
Made-with: Cursor
118 lines
2.8 KiB
TypeScript
118 lines
2.8 KiB
TypeScript
import type { PushPermission, ShellPermission } from "../external.ts";
|
|
import { apiFetch } from "./apiFetch.ts";
|
|
import type { RepoContext } from "./github.ts";
|
|
|
|
export interface Mode {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
prompt: string;
|
|
}
|
|
|
|
export interface RepoSettings {
|
|
model: string | null;
|
|
modes: Mode[];
|
|
setupScript: string | null;
|
|
postCheckoutScript: string | null;
|
|
prepushScript: string | null;
|
|
push: PushPermission;
|
|
shell: ShellPermission;
|
|
prApproveEnabled: boolean;
|
|
modeInstructions: Record<string, string>;
|
|
learnings: string | null;
|
|
}
|
|
|
|
export interface RunContext {
|
|
settings: RepoSettings;
|
|
apiToken: string;
|
|
oss: boolean;
|
|
proxyModel?: string | undefined;
|
|
dbSecrets?: Record<string, string> | undefined;
|
|
}
|
|
|
|
const defaultSettings: RepoSettings = {
|
|
model: null,
|
|
modes: [],
|
|
setupScript: null,
|
|
postCheckoutScript: null,
|
|
prepushScript: null,
|
|
push: "restricted",
|
|
shell: "restricted",
|
|
prApproveEnabled: false,
|
|
modeInstructions: {},
|
|
learnings: null,
|
|
};
|
|
|
|
const defaultRunContext: RunContext = {
|
|
settings: defaultSettings,
|
|
apiToken: "",
|
|
oss: false,
|
|
};
|
|
|
|
/**
|
|
* fetch run context from Pullfrog API
|
|
* returns settings + API token for subsequent calls
|
|
* returns defaults if fetch fails
|
|
*/
|
|
export async function fetchRunContext(params: {
|
|
token: string;
|
|
repoContext: RepoContext;
|
|
oidcToken?: string | undefined;
|
|
}): Promise<RunContext> {
|
|
const timeoutMs = 30000;
|
|
const controller = new AbortController();
|
|
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
|
|
try {
|
|
const headers: Record<string, string> = {
|
|
Authorization: `Bearer ${params.token}`,
|
|
"Content-Type": "application/json",
|
|
};
|
|
if (params.oidcToken) {
|
|
headers["X-GitHub-OIDC-Token"] = params.oidcToken;
|
|
}
|
|
|
|
const response = await apiFetch({
|
|
path: `/api/repo/${params.repoContext.owner}/${params.repoContext.name}/run-context`,
|
|
headers,
|
|
signal: controller.signal,
|
|
});
|
|
|
|
clearTimeout(timeoutId);
|
|
|
|
if (!response.ok) {
|
|
return defaultRunContext;
|
|
}
|
|
|
|
const data = (await response.json()) as {
|
|
settings: RepoSettings | null;
|
|
apiToken: string;
|
|
oss?: boolean;
|
|
proxyModel?: string;
|
|
dbSecrets?: Record<string, string>;
|
|
} | null;
|
|
|
|
if (data === null) {
|
|
return defaultRunContext;
|
|
}
|
|
|
|
return {
|
|
settings: {
|
|
...defaultSettings,
|
|
...data.settings,
|
|
modes: data.settings?.modes ?? [],
|
|
setupScript: data.settings?.setupScript ?? null,
|
|
postCheckoutScript: data.settings?.postCheckoutScript ?? null,
|
|
prepushScript: data.settings?.prepushScript ?? null,
|
|
},
|
|
apiToken: data.apiToken,
|
|
oss: data.oss ?? false,
|
|
proxyModel: data.proxyModel,
|
|
dbSecrets: data.dbSecrets,
|
|
};
|
|
} catch {
|
|
clearTimeout(timeoutId);
|
|
return defaultRunContext;
|
|
}
|
|
}
|