-
Notifications
You must be signed in to change notification settings - Fork 64
fix(tasks): restore cloud artifact uploads #3858
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
4bbe7a7
216282a
37f780d
93fc7d0
77c4658
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ import type { SignedCommitResult } from "@posthog/git/signed-commit"; | |
| import { PostHogAPIClient } from "./posthog-api"; | ||
|
|
||
| const SANDBOX_ENV_FILE = "/tmp/agent-env"; | ||
| const SANDBOX_OAUTH_ENV_FILE = "/tmp/agent-oauth-env"; | ||
|
|
||
| /** | ||
| * Best-effort "commit hook": after a successful signed-commit push, record one `commit` | ||
|
|
@@ -11,11 +12,10 @@ const SANDBOX_ENV_FILE = "/tmp/agent-env"; | |
| * endpoint reads the `X-PostHog-Task-Id` header, never the model. | ||
| * | ||
| * Credentials come from the sandbox environment (`POSTHOG_API_URL` / | ||
| * `POSTHOG_PERSONAL_API_KEY` / `POSTHOG_PROJECT_ID`), preferring the live agentsh env file | ||
| * for the key so a mid-session token refresh is picked up — the same pattern as | ||
| * `resolveGithubToken`. Works identically from the Claude in-process server and the Codex | ||
| * stdio child (both inherit the sandbox env). Never throws: a failed artefact post must not | ||
| * fail the commit that already landed. | ||
| * `POSTHOG_PERSONAL_API_KEY` / `POSTHOG_PROJECT_ID`), preferring the dedicated live agentsh | ||
| * OAuth file for the key so a mid-session token refresh is picked up — the same pattern as | ||
| * `resolveGithubToken`. Never throws: a failed artefact post must not fail the commit that | ||
| * already landed. | ||
| */ | ||
|
|
||
| interface SandboxPosthogApi { | ||
|
|
@@ -41,28 +41,48 @@ function readSandboxEnvFile(envFilePath: string): Record<string, string> { | |
| } | ||
| } | ||
|
|
||
| function readSandboxOauthToken(oauthEnvFilePath: string): string | undefined { | ||
| let raw: string; | ||
| try { | ||
| raw = readFileSync(oauthEnvFilePath, "utf8"); | ||
| } catch { | ||
| // The dedicated credential channel is mandatory. Missing and unreadable | ||
| // files both fail closed. | ||
| return undefined; | ||
| } | ||
|
|
||
| // The backend revokes OAuth access by truncating this managed file. Its | ||
| // presence is authoritative even when empty. | ||
| if (raw.trim() === "") { | ||
| return ""; | ||
| } | ||
| const oauthEnv = readSandboxEnvFile(oauthEnvFilePath); | ||
| return oauthEnv.POSTHOG_PERSONAL_API_KEY ?? ""; | ||
| } | ||
|
|
||
| export function resolveSandboxPosthogApi( | ||
| env: Record<string, string | undefined> = process.env, | ||
| envFilePath: string = SANDBOX_ENV_FILE, | ||
| oauthEnvFilePath: string = SANDBOX_OAUTH_ENV_FILE, | ||
| ): SandboxPosthogApi | undefined { | ||
| const fileEnv = readSandboxEnvFile(envFilePath); | ||
| const oauthToken = readSandboxOauthToken(oauthEnvFilePath); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Medium: Managed OAuth token can be redirected to an untrusted host The managed token is paired with |
||
| const apiUrl = fileEnv.POSTHOG_API_URL ?? env.POSTHOG_API_URL; | ||
| const apiKey = | ||
| fileEnv.POSTHOG_PERSONAL_API_KEY ?? env.POSTHOG_PERSONAL_API_KEY; | ||
| const projectId = Number( | ||
| fileEnv.POSTHOG_PROJECT_ID ?? env.POSTHOG_PROJECT_ID, | ||
| ); | ||
| if (!apiUrl || !apiKey || !Number.isFinite(projectId) || projectId <= 0) { | ||
| if (!apiUrl || !oauthToken || !Number.isFinite(projectId) || projectId <= 0) { | ||
| return undefined; | ||
| } | ||
| return { apiUrl, apiKey, projectId }; | ||
| return { apiUrl, apiKey: oauthToken, projectId }; | ||
| } | ||
|
|
||
| export function createSandboxPosthogClient( | ||
| env?: Record<string, string | undefined>, | ||
| envFilePath?: string, | ||
| oauthEnvFilePath?: string, | ||
| ): PostHogAPIClient | undefined { | ||
| const api = resolveSandboxPosthogApi(env, envFilePath); | ||
| const api = resolveSandboxPosthogApi(env, envFilePath, oauthEnvFilePath); | ||
| if (!api) { | ||
| return undefined; | ||
| } | ||
|
|
@@ -80,13 +100,18 @@ export async function reportCommitArtefacts(opts: { | |
| message: string; | ||
| env?: Record<string, string | undefined>; | ||
| envFilePath?: string; | ||
| oauthEnvFilePath?: string; | ||
| }): Promise<void> { | ||
| const { taskId, result, message } = opts; | ||
| if (!taskId) { | ||
| return; // Local/desktop run — no task to attribute or associate through. | ||
| } | ||
| try { | ||
| const client = createSandboxPosthogClient(opts.env, opts.envFilePath); | ||
| const client = createSandboxPosthogClient( | ||
| opts.env, | ||
| opts.envFilePath, | ||
| opts.oauthEnvFilePath, | ||
| ); | ||
| if (!client) { | ||
| return; // No sandbox PostHog credentials — nothing to report to. | ||
| } | ||
|
|
@@ -121,12 +146,17 @@ export async function reportTaskRunBranch(opts: { | |
| branch: string; | ||
| env?: Record<string, string | undefined>; | ||
| envFilePath?: string; | ||
| oauthEnvFilePath?: string; | ||
| }): Promise<void> { | ||
| if (!opts.taskId || !opts.taskRunId) { | ||
| return; | ||
| } | ||
| try { | ||
| const client = createSandboxPosthogClient(opts.env, opts.envFilePath); | ||
| const client = createSandboxPosthogClient( | ||
| opts.env, | ||
| opts.envFilePath, | ||
| opts.oauthEnvFilePath, | ||
| ); | ||
| if (!client) { | ||
| return; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe here we are reading thefile a second time after validating the first snapshot, a refresh between those reads could validate one generation and parse another. Could we parse
POSTHOG_PERSONAL_API_KEYdirectly from raw so each resolution uses one consistent snapshot?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in 93fc7d0: the resolver now parses the token directly from the single
readFileSyncsnapshot, so validation and parsing cannot observe different file generations.