Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit db376d4

Browse files
authored
Merge branch 'main' into posthog-code/collapsible-inbox-report-sections
2 parents be05136 + 4cf4612 commit db376d4

103 files changed

Lines changed: 3992 additions & 925 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/code-storybook.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ jobs:
169169

170170
- name: Install Visual Review CLI
171171
if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'push'
172-
run: cd vr-cli/products/visual_review/cli && npm ci && npm run build && npm link
172+
run: cd vr-cli/products/visual_review/cli && (npm ci || npm install) && npm run build && npm link
173173

174174
- name: Submit snapshots to Visual Review
175175
# Fork PRs can't read the token; their captures still ran above, so a

apps/code/src/main/window.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,11 +208,14 @@ export function createWindow(): void {
208208
const platformWindowConfig =
209209
process.platform === "darwin"
210210
? {
211-
titleBarStyle: "hiddenInset" as const,
211+
// "hidden", not "hiddenInset": hiddenInset keeps macOS's own inset and
212+
// ignores trafficLightPosition's y, which parked the dots near the
213+
// bottom of the bar. "hidden" honours the position we ask for.
214+
titleBarStyle: "hidden" as const,
212215
// Centre the traffic lights vertically with the title bar's back/forward
213216
// buttons (40px bar, 24px buttons → centre at y=20; 12px dots → top at 14).
214217
// x mirrors y so the inset from the top and the left match.
215-
trafficLightPosition: { x: 14, y: 14 },
218+
trafficLightPosition: { x: 14, y: 12 },
216219
// Exposes the titlebar-area-* CSS env vars so the renderer can
217220
// clear the traffic lights exactly; their size varies by macOS
218221
// version (bigger on Tahoe), so it must not hardcode a width.

apps/web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"@posthog/core": "workspace:*",
2020
"@posthog/di": "workspace:*",
2121
"@posthog/host-router": "workspace:*",
22+
"@posthog/harness": "workspace:*",
2223
"@posthog/host-trpc": "workspace:*",
2324
"@posthog/platform": "workspace:*",
2425
"@posthog/shared": "workspace:*",

apps/web/src/web-host-router.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { fetchPosthogPiModelCatalog } from "@posthog/agent/pi/model-catalog";
21
import { getLlmGatewayUrl } from "@posthog/agent/posthog-api";
32
import type { AuthService } from "@posthog/core/auth/auth";
43
import { AUTH_SERVICE } from "@posthog/core/auth/auth.module";
54
import { TEAM_SKILLS_SERVICE } from "@posthog/core/skills/identifiers";
65
import type { TeamSkillsService } from "@posthog/core/skills/teamSkillsService";
76
import { resolveService } from "@posthog/di/container";
7+
import { fetchPosthogPiModelCatalog } from "@posthog/harness/extensions/posthog-provider/model-catalog";
88
import { analyticsRouter } from "@posthog/host-router/routers/analytics.router";
99
import { authRouter } from "@posthog/host-router/routers/auth.router";
1010
import { canvasDataRouter } from "@posthog/host-router/routers/canvas-data.router";

packages/agent/package.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,6 @@
5252
"types": "./dist/pi/types.d.ts",
5353
"import": "./dist/pi/types.js"
5454
},
55-
"./pi/model-catalog": {
56-
"types": "./dist/pi/model-catalog.d.ts",
57-
"import": "./dist/pi/model-catalog.js"
58-
},
5955
"./pr-url-detector": {
6056
"types": "./dist/pr-url-detector.d.ts",
6157
"import": "./dist/pr-url-detector.js"

packages/agent/src/adapters/claude/session/options.test.ts

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,14 +376,18 @@ describe("buildSessionOptions", () => {
376376
name: "omits the team_id header when POSTHOG_PROJECT_ID is unset",
377377
projectId: undefined,
378378
existingHeaders: undefined,
379-
expected: "x-posthog-use-bedrock-fallback: true",
379+
expected: [
380+
"x-posthog-property-$ai_session_id: test-session",
381+
"x-posthog-use-bedrock-fallback: true",
382+
].join("\n"),
380383
},
381384
{
382385
name: "forwards POSTHOG_PROJECT_ID as the team_id attribution header",
383386
projectId: "42",
384387
existingHeaders: undefined,
385388
expected: [
386389
"x-posthog-property-team_id: 42",
390+
"x-posthog-property-$ai_session_id: test-session",
387391
"x-posthog-use-bedrock-fallback: true",
388392
].join("\n"),
389393
},
@@ -394,6 +398,7 @@ describe("buildSessionOptions", () => {
394398
expected: [
395399
"x-posthog-property-task_id: task-abc",
396400
"x-posthog-property-team_id: 42",
401+
"x-posthog-property-$ai_session_id: test-session",
397402
"x-posthog-use-bedrock-fallback: true",
398403
].join("\n"),
399404
},
@@ -411,6 +416,101 @@ describe("buildSessionOptions", () => {
411416
expect(headers).toBe(expected);
412417
});
413418
});
419+
420+
describe("gateway turn tracing env", () => {
421+
const KEYS = [
422+
"CLAUDE_CODE_ENABLE_TELEMETRY",
423+
"CLAUDE_CODE_ENHANCED_TELEMETRY_BETA",
424+
"CLAUDE_CODE_PROPAGATE_TRACEPARENT",
425+
"OTEL_TRACES_EXPORTER",
426+
"OTEL_EXPORTER_OTLP_PROTOCOL",
427+
"OTEL_EXPORTER_OTLP_ENDPOINT",
428+
"TRACEPARENT",
429+
"TRACESTATE",
430+
] as const;
431+
const original: Partial<Record<string, string | undefined>> = {};
432+
433+
beforeEach(() => {
434+
for (const key of KEYS) {
435+
original[key] = process.env[key];
436+
delete process.env[key];
437+
}
438+
});
439+
440+
afterEach(() => {
441+
for (const key of KEYS) {
442+
const value = original[key];
443+
if (value === undefined) {
444+
delete process.env[key];
445+
} else {
446+
process.env[key] = value;
447+
}
448+
}
449+
});
450+
451+
const gatewayEnv = {
452+
anthropicBaseUrl: "https://gateway.example",
453+
anthropicAuthToken: "tok",
454+
openaiBaseUrl: "https://gateway.example/v1",
455+
openaiApiKey: "tok",
456+
};
457+
458+
it("enables per-turn traceparent when routed through the gateway", () => {
459+
const env = buildSessionOptions({ ...makeParams(), gatewayEnv }).env;
460+
461+
expect(env?.CLAUDE_CODE_ENABLE_TELEMETRY).toBe("1");
462+
expect(env?.CLAUDE_CODE_ENHANCED_TELEMETRY_BETA).toBe("1");
463+
expect(env?.CLAUDE_CODE_PROPAGATE_TRACEPARENT).toBe("1");
464+
expect(env?.OTEL_TRACES_EXPORTER).toBe("otlp");
465+
expect(env?.OTEL_EXPORTER_OTLP_PROTOCOL).toBe("http/json");
466+
expect(env?.OTEL_EXPORTER_OTLP_ENDPOINT).toBe("http://127.0.0.1:9");
467+
});
468+
469+
it("honors a caller-supplied OTLP endpoint", () => {
470+
process.env.OTEL_EXPORTER_OTLP_ENDPOINT =
471+
"http://collector.internal:4318";
472+
473+
const env = buildSessionOptions({ ...makeParams(), gatewayEnv }).env;
474+
475+
expect(env?.OTEL_EXPORTER_OTLP_ENDPOINT).toBe(
476+
"http://collector.internal:4318",
477+
);
478+
});
479+
480+
it("pins exporter and protocol so an inherited none can't disable tracing", () => {
481+
process.env.OTEL_TRACES_EXPORTER = "none";
482+
process.env.OTEL_EXPORTER_OTLP_PROTOCOL = "grpc";
483+
484+
const env = buildSessionOptions({ ...makeParams(), gatewayEnv }).env;
485+
486+
expect(env?.OTEL_TRACES_EXPORTER).toBe("otlp");
487+
expect(env?.OTEL_EXPORTER_OTLP_PROTOCOL).toBe("http/json");
488+
});
489+
490+
it("strips inherited TRACEPARENT so turns keep distinct trace ids", () => {
491+
process.env.TRACEPARENT =
492+
"00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01";
493+
process.env.TRACESTATE = "vendor=x";
494+
495+
const env = buildSessionOptions({ ...makeParams(), gatewayEnv }).env;
496+
497+
expect(env?.TRACEPARENT).toBeUndefined();
498+
expect(env?.TRACESTATE).toBeUndefined();
499+
});
500+
501+
it("leaves BYOK sessions untouched", () => {
502+
process.env.TRACEPARENT =
503+
"00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01";
504+
505+
const env = buildSessionOptions(makeParams()).env;
506+
507+
expect(env?.CLAUDE_CODE_ENABLE_TELEMETRY).toBeUndefined();
508+
expect(env?.CLAUDE_CODE_PROPAGATE_TRACEPARENT).toBeUndefined();
509+
expect(env?.TRACEPARENT).toBe(
510+
"00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01",
511+
);
512+
});
513+
});
414514
});
415515

416516
describe("buildSystemPrompt", () => {

packages/agent/src/adapters/claude/session/options.ts

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,10 @@ function buildMcpServers(
151151
};
152152
}
153153

154-
function buildEnvironment(gateway?: GatewayEnv): Record<string, string> {
154+
function buildEnvironment(
155+
gateway?: GatewayEnv,
156+
sessionId?: string,
157+
): Record<string, string> {
155158
// Custom HTTP headers reach the model only through the Claude CLI subprocess,
156159
// which reads them from this env var (newline-delimited `name: value` lines)
157160
// — the SDK has no direct header option. We finalize them here, the single
@@ -174,6 +177,11 @@ function buildEnvironment(gateway?: GatewayEnv): Record<string, string> {
174177
if (projectId) {
175178
headerLines.push(buildGatewayPropertyHeaders({ team_id: projectId }));
176179
}
180+
if (sessionId) {
181+
headerLines.push(
182+
buildGatewayPropertyHeaders({ $ai_session_id: sessionId }),
183+
);
184+
}
177185
// Route to AWS Bedrock as a fallback when Anthropic returns 5xx
178186
headerLines.push("x-posthog-use-bedrock-fallback: true");
179187
const customHeaders = headerLines.join("\n");
@@ -185,8 +193,31 @@ function buildEnvironment(gateway?: GatewayEnv): Record<string, string> {
185193
// sessions that genuinely need MCP tools available on turn 1.
186194
const mcpNonblocking = process.env.MCP_CONNECTION_NONBLOCKING;
187195

188-
return {
196+
// Every var is load-bearing (ablation-tested): the CLI stamps the per-turn
197+
// traceparent only once its OTel tracer initializes, and the dead endpoint
198+
// keeps the throwaway spans off any local collector. Exporter and protocol
199+
// are pinned rather than inherited — an ambient OTEL_TRACES_EXPORTER=none or
200+
// unknown protocol registers no tracer and silently drops the traceparent;
201+
// the endpoint stays overridable for a real collector.
202+
// Residual risk: a repo's .claude/settings.json `env` is applied over these
203+
// inside the CLI and can redirect the endpoint or turn on content capture
204+
// (OTEL_LOG_TOOL_CONTENT, …) — pre-existing settingSources exposure, not
205+
// closable from here; hardening tracked separately.
206+
const gatewayTracing: Record<string, string> = gateway?.anthropicBaseUrl
207+
? {
208+
CLAUDE_CODE_ENABLE_TELEMETRY: "1",
209+
CLAUDE_CODE_ENHANCED_TELEMETRY_BETA: "1",
210+
CLAUDE_CODE_PROPAGATE_TRACEPARENT: "1",
211+
OTEL_TRACES_EXPORTER: "otlp",
212+
OTEL_EXPORTER_OTLP_PROTOCOL: "http/json",
213+
OTEL_EXPORTER_OTLP_ENDPOINT:
214+
process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://127.0.0.1:9",
215+
}
216+
: {};
217+
218+
const env: Record<string, string> = {
189219
...process.env,
220+
...gatewayTracing,
190221
// Explicit gateway values win over whatever happens to be in process.env.
191222
// This prevents concurrent Agent instances from clobbering each other's
192223
// gateway config when process.env was mutated globally.
@@ -212,6 +243,13 @@ function buildEnvironment(gateway?: GatewayEnv): Record<string, string> {
212243
}),
213244
ANTHROPIC_CUSTOM_HEADERS: customHeaders,
214245
};
246+
if (gateway?.anthropicBaseUrl) {
247+
// The CLI parents every turn under an inherited ambient TRACEPARENT,
248+
// collapsing the per-turn trace ids this block exists to produce.
249+
delete env.TRACEPARENT;
250+
delete env.TRACESTATE;
251+
}
252+
return env;
215253
}
216254

217255
function buildHooks(
@@ -473,7 +511,7 @@ export function buildSessionOptions(params: BuildOptionsParams): Options {
473511
params.mcpServers,
474512
loadUserClaudeJsonMcpServers(params.cwd, params.logger),
475513
),
476-
env: buildEnvironment(params.gatewayEnv),
514+
env: buildEnvironment(params.gatewayEnv, params.sessionId),
477515
hooks: buildHooks(
478516
params.userProvidedOptions?.hooks,
479517
params.onModeChange,

packages/agent/src/adapters/codex-app-server/spawn.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,18 @@ describe("buildAppServerArgs", () => {
5757
);
5858
});
5959

60+
it("quotes $-prefixed posthog property header keys in the TOML table", () => {
61+
const args = buildAppServerArgs({
62+
binaryPath: "/bundle/codex",
63+
apiBaseUrl: "https://gateway.example/v1",
64+
httpHeaders: { "x-posthog-property-$ai_session_id": "task-123" },
65+
});
66+
67+
expect(args).toContain(
68+
'model_providers.posthog.http_headers={ "x-posthog-property-$ai_session_id" = "task-123" }',
69+
);
70+
});
71+
6072
it("omits http_headers when none are provided or the provider is unset", () => {
6173
const withoutHeaders = buildAppServerArgs({
6274
binaryPath: "/bundle/codex",

packages/agent/src/adapters/local-tools/tools/signed-commit.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,41 @@ describe("signed-commit tool handler", () => {
117117
});
118118
});
119119

120+
it("persists the branch when cwd uses an equivalent path representation", async () => {
121+
await signedCommitTool.handler(
122+
{
123+
cwd: "/tmp/workspace/repos/posthog/code/.",
124+
token: "ghs_x",
125+
taskId: "task-1",
126+
taskRunId: "run-1",
127+
},
128+
{ message: "chore: bump", cwd: "." },
129+
);
130+
131+
expect(reportTaskRunBranch).toHaveBeenCalledWith({
132+
taskId: "task-1",
133+
taskRunId: "run-1",
134+
branch: "posthog-code/feature",
135+
});
136+
});
137+
138+
it("does not persist a branch created in a sibling repository", async () => {
139+
await signedCommitTool.handler(
140+
{
141+
cwd: "/tmp/workspace/repos/posthog/code",
142+
token: "ghs_x",
143+
taskId: "task-1",
144+
taskRunId: "run-1",
145+
},
146+
{
147+
message: "chore: bump",
148+
cwd: "/tmp/workspace/repos/posthog/grafana-dashboards",
149+
},
150+
);
151+
152+
expect(reportTaskRunBranch).not.toHaveBeenCalled();
153+
});
154+
120155
it("returns the no-token error without invoking createSignedCommit", async () => {
121156
const savedGh = process.env.GH_TOKEN;
122157
const savedGithub = process.env.GITHUB_TOKEN;

packages/agent/src/adapters/local-tools/tools/signed-git-tool.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,14 @@ export function defineSignedGitTool<S extends z.ZodRawShape, R>(opts: {
4343
string,
4444
unknown
4545
>;
46-
const cwd = argCwd ? path.resolve(ctx.cwd, argCwd) : ctx.cwd;
46+
const taskRepositoryCwd = path.resolve(ctx.cwd);
47+
const cwd = argCwd
48+
? path.resolve(taskRepositoryCwd, argCwd)
49+
: taskRepositoryCwd;
4750
return opts.run(
4851
{
4952
cwd,
53+
taskRepositoryCwd,
5054
token,
5155
taskId: ctx.taskId,
5256
taskRunId: ctx.taskRunId,

0 commit comments

Comments
 (0)