Skip to content

Telemetry: Add project age#33910

Merged
valentinpalkovic merged 5 commits into
nextfrom
shilman/add-project-since
Feb 24, 2026
Merged

Telemetry: Add project age#33910
valentinpalkovic merged 5 commits into
nextfrom
shilman/add-project-since

Conversation

@shilman
Copy link
Copy Markdown
Member

@shilman shilman commented Feb 24, 2026

Closes N/A

What I did

Collect the project age so we can understand whether Storybook is being installed into new projects

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

  1. Run create-storybook in a project with STORYBOOK_TELEMETRY_DEBUG=1 and verify the output

Documentation

Updated telemetry.mdx to include new data being collected

🦋 Canary release

This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the @storybookjs/core team here.

core team members can create a canary release here or locally with gh workflow run --repo storybookjs/storybook publish.yml --field pr=<PR_NUMBER>

Summary by CodeRabbit

  • New Features

    • Telemetry now captures the project's creation date and includes it as a projectSince value in the public telemetry context payload.
  • Tests

    • Expanded test coverage for project creation date detection across successful, empty, and error scenarios; added deterministic mocks for command execution and setup.
  • Documentation

    • Updated telemetry docs and example event payloads to show the new projectSince field.

@shilman shilman added maintenance User-facing maintenance tasks patch:yes Bugfix & documentation PR that need to be picked to main branch telemetry ci:normal labels Feb 24, 2026
@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Feb 24, 2026

View your CI Pipeline Execution ↗ for commit 35cc133

Command Status Duration Result
nx run-many -t compile,check,knip,test,pretty-d... ❌ Failed 8m 10s View ↗

☁️ Nx Cloud last updated this comment at 2026-02-24 08:55:17 UTC

@shilman shilman force-pushed the shilman/add-project-since branch from 57e395a to bc9b9df Compare February 24, 2026 04:19
@valentinpalkovic valentinpalkovic marked this pull request as ready for review February 24, 2026 08:01
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 24, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3052206 and 35cc133.

📒 Files selected for processing (1)
  • docs/configure/telemetry.mdx
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/configure/telemetry.mdx

📝 Walkthrough

Walkthrough

Adds a memoized getProjectSince() that runs a git command via executeCommandSync to obtain the repo's first commit date, exposes its timestamp as projectSince in telemetry context, replaces execSync usage with executeCommandSync, and extends tests and docs for the new behavior.

Changes

Cohort / File(s) Summary
Core telemetry logic
code/core/src/telemetry/anonymous-id.ts, code/core/src/telemetry/telemetry.ts
Replaced direct execSync with executeCommandSync; added exported, memoized `getProjectSince(): Date
Tests
code/core/src/telemetry/anonymous-id.test.ts
Expanded tests with Vitest vi mocks for executeCommandSync and getProjectRoot; added tests for getProjectSince (valid output, empty output, failure) and updated getAnonymousProjectId tests to use the new command flow.
Docs
docs/configure/telemetry.mdx
Added projectSince to the example telemetry event payload in the dev event documentation.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI / Telemetry Sender
    participant Anonymous as anonymous-id module
    participant Exec as executeCommandSync
    participant Git as git (local repo)

    CLI->>Anonymous: build telemetry context
    Anonymous->>Exec: run "git log --reverse --format=%cI"
    Exec->>Git: spawn git process
    Git-->>Exec: stdout (first-commit date) or error
    Exec-->>Anonymous: stdout or failure
    Anonymous->>Anonymous: parse stdout -> Date | undefined (memoize)
    Anonymous-->>CLI: context including projectSince (ms) if available
    CLI->>CLI: send telemetry payload
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Core: Add preview-first-load telemetry #32770 — Modifies telemetry send flow and caching/preview-first-load behavior; touches related telemetry send logic and may interact with how additional context fields (like projectSince) are handled.

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

Copy link
Copy Markdown
Contributor

@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: 2

🧹 Nitpick comments (1)
code/core/src/telemetry/anonymous-id.ts (1)

57-79: Limit git log cost and memoize project age.

git log --reverse emits the full history every call, and getProjectSince() can be invoked per telemetry event. Limit output to a single commit and cache the result to avoid repeated process spawns/timeouts.

♻️ Proposed refactor
+let projectSinceCache: Date | undefined;
+let projectSinceResolved = false;
+
 export const getProjectSince = () => {
+  if (projectSinceResolved) {
+    return projectSinceCache;
+  }
+  projectSinceResolved = true;
+
   try {
     const dateBuffer = executeCommandSync({
       command: 'git',
-      args: ['log', '--reverse', '--format=%cd', '--date=iso'],
+      args: ['log', '--reverse', '--max-count=1', '--format=%cd', '--date=iso'],
       timeout: 1000,
     });

     const firstLine = String(dateBuffer).trim().split('\n')[0];

     const date = new Date(firstLine);

     if (Number.isNaN(date.getTime())) {
-      return undefined;
+      projectSinceCache = undefined;
+      return undefined;
     }

-    return date;
+    projectSinceCache = date;
+    return date;
   } catch (_) {
     //
   }

-  return undefined;
+  projectSinceCache = undefined;
+  return undefined;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/telemetry/anonymous-id.ts` around lines 57 - 79,
getProjectSince currently shells out to git with --reverse and returns the first
line each time; change it to request only the first commit (use git log
--reverse --max-count=1 or git log -1 --format=%cd --date=iso) to avoid
streaming full history, and memoize the computed Date (store it in a
module-level variable, e.g., cachedProjectSince) so subsequent calls return the
cached value instead of calling executeCommandSync again; keep the same error
handling and return undefined on failure or invalid date.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@code/core/src/telemetry/anonymous-id.test.ts`:
- Around line 1-14: The tests currently set per-test inline mocks and rely on
real git output; move the executeCommandSync mock setup into a beforeEach block
and use vi.mocked(executeCommandSync) to set return values there so all tests
get controlled, deterministic git output (mock the git command responses used by
getProjectSince/normalizeGitUrl/unhashedProjectId); remove per-test mock
implementations (lines 125–137) and instead in beforeEach call
vi.mocked(executeCommandSync).mockImplementation(...) or .mockReturnValue(...)
to stabilize the git-dependent test — keep the async factory vi.mock(...) as-is
and do not add the { spy: true } option.

In `@docs/configure/telemetry.mdx`:
- Around line 65-71: The example payload's "projectSince" is shown as a string
but the implementation sends a numeric timestamp (Date.getTime()); update the
example JSON so "projectSince" is a number (no quotes) and uses a representative
numeric value (e.g. 1717334400000) to match the actual behavior and avoid
confusion—modify the JSON example containing "context" and the "projectSince"
field accordingly.

---

Nitpick comments:
In `@code/core/src/telemetry/anonymous-id.ts`:
- Around line 57-79: getProjectSince currently shells out to git with --reverse
and returns the first line each time; change it to request only the first commit
(use git log --reverse --max-count=1 or git log -1 --format=%cd --date=iso) to
avoid streaming full history, and memoize the computed Date (store it in a
module-level variable, e.g., cachedProjectSince) so subsequent calls return the
cached value instead of calling executeCommandSync again; keep the same error
handling and return undefined on failure or invalid date.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a2c607 and bc5c582.

📒 Files selected for processing (4)
  • code/core/src/telemetry/anonymous-id.test.ts
  • code/core/src/telemetry/anonymous-id.ts
  • code/core/src/telemetry/telemetry.ts
  • docs/configure/telemetry.mdx

Comment on lines +1 to +14
import { describe, expect, it, vi } from 'vitest';

import { normalizeGitUrl, unhashedProjectId } from './anonymous-id';
import { executeCommandSync } from 'storybook/internal/common';

import { getProjectSince, normalizeGitUrl, unhashedProjectId } from './anonymous-id';

vi.mock(import('storybook/internal/common'), async (actualModule) => {
const actual = await actualModule();

return {
...actual,
executeCommandSync: vi.fn(actual.executeCommandSync),
};
});
Copy link
Copy Markdown
Contributor

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

🌐 Web query:

Vitest vi.mock async factory with spy:true option syntax

💡 Result:

spy: true is an alternative to providing a factory—you can’t pass { spy: true } and an async factory in the same vi.mock() call (the 2nd argument is either MockOptions or MockFactory). [1]

Auto-spy the whole module (no factory)

import * as mod from './mod'

vi.mock('./mod', { spy: true })

This keeps original implementations but wraps exports so you can assert calls. [2][1]

If you need an async “factory-like” partial mock, wrap originals as spies yourself

vi.mock('./mod', async (importOriginal) => {
  const actual = await importOriginal<typeof import('./mod')>()
  return {
    ...actual,
    someFn: vi.fn(actual.someFn), // spy that still calls through
    // or override others as needed
  }
})

Vitest allows async factories and gives you importOriginal to access the real module. [1]

Sources:
[1] Vitest vi.mock API reference (types/overloads; async factory support)
[2] Vitest “Mocking Modules” guide ({ spy: true } behavior and usage)


🏁 Script executed:

yarn lint:js:cmd code/core/src/telemetry/anonymous-id.test.ts 2>&1 || true

Repository: storybookjs/storybook

Length of output: 283


🏁 Script executed:

cat -n code/core/src/telemetry/anonymous-id.test.ts | head -150

Repository: storybookjs/storybook

Length of output: 5332


Move mock implementations to beforeEach blocks and stabilize git-dependent test.

The current mock setup has inline mock implementations inside individual test cases (lines 125–137), which violates Vitest patterns. Additionally, the first test relies on real git history and will fail on shallow clones. Move all mock behavior to beforeEach, use vi.mocked() to access the mock, and mock the git output for all cases. The async factory pattern is valid as-is; do not add { spy: true } as that option is an alternative to the factory, not compatible with it.

Refactor mock setup
 import { describe, expect, it, vi } from 'vitest';
+import { beforeEach } from 'vitest';
 
 import { executeCommandSync } from 'storybook/internal/common';
 
 import { getProjectSince, normalizeGitUrl, unhashedProjectId } from './anonymous-id';
 
 vi.mock('storybook/internal/common', async (importOriginal) => {
-  const actual = await importOriginal();
+  const actual = await importOriginal<typeof import('storybook/internal/common')>();
 
   return {
     ...actual,
     executeCommandSync: vi.fn(actual.executeCommandSync),
   };
 });
+
+const mockedExecuteCommandSync = vi.mocked(executeCommandSync);
+
+beforeEach(() => {
+  mockedExecuteCommandSync.mockReset();
+  mockedExecuteCommandSync.mockReturnValue('2015-12-11T10:54:01.000Z');
+});
 
 describe('getProjectSince', () => {
   it('returns the Storybook creation date from git log output', () => {
     expect(getProjectSince()).toEqual(new Date('2015-12-11T10:54:01.000Z'));
   });
 
   it('returns undefined if git log output is empty', () => {
-    vi.mocked(executeCommandSync).mockReturnValue('');
+    mockedExecuteCommandSync.mockReturnValue('');
 
     expect(getProjectSince()).toBeUndefined();
   });
 
   it('returns undefined if git log fails', () => {
-    vi.mocked(executeCommandSync).mockImplementation(() => {
+    mockedExecuteCommandSync.mockImplementation(() => {
       throw new Error('git not available');
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/telemetry/anonymous-id.test.ts` around lines 1 - 14, The tests
currently set per-test inline mocks and rely on real git output; move the
executeCommandSync mock setup into a beforeEach block and use
vi.mocked(executeCommandSync) to set return values there so all tests get
controlled, deterministic git output (mock the git command responses used by
getProjectSince/normalizeGitUrl/unhashedProjectId); remove per-test mock
implementations (lines 125–137) and instead in beforeEach call
vi.mocked(executeCommandSync).mockImplementation(...) or .mockReturnValue(...)
to stabilize the git-dependent test — keep the async factory vi.mock(...) as-is
and do not add the { spy: true } option.

Comment thread docs/configure/telemetry.mdx Outdated
Copilot AI review requested due to automatic review settings February 24, 2026 08:12
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds telemetry for tracking project age by collecting the project creation date from git history. The implementation retrieves the timestamp of the first git commit to help understand whether Storybook is being installed into new or existing projects.

Changes:

  • Added getProjectSince() function to retrieve the project creation date from git log
  • Integrated projectSince field into telemetry context payload
  • Updated telemetry documentation to include the new field in example output

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
code/core/src/telemetry/anonymous-id.ts Added getProjectSince() function that executes git log --reverse --format=%cd --date=iso to get the first commit date
code/core/src/telemetry/telemetry.ts Integrated projectSince into the telemetry context by calling getProjectSince()?.getTime()
code/core/src/telemetry/anonymous-id.test.ts Added comprehensive unit tests covering success, empty output, and error scenarios
docs/configure/telemetry.mdx Updated example telemetry output to show the new projectSince field in the context object

Comment thread docs/configure/telemetry.mdx Outdated
"nodeVersion": "24.11.0",
"storybookVersion": "10.3.0-alpha.9",
"cliVersion": "10.3.0-alpha.9",
"projectSince": "1717334400000"
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

The projectSince value should be a number, not a string. Since the code uses getProjectSince()?.getTime() which returns a number (or undefined), and this is serialized with JSON.stringify, the value will appear as a number in the JSON output. The documentation should show it as "projectSince": 1717334400000 instead of "projectSince": "1717334400000".

Suggested change
"projectSince": "1717334400000"
"projectSince": 1717334400000

Copilot uses AI. Check for mistakes.
Comment on lines +129 to +147
describe('getProjectSince', () => {
it('returns the Storybook creation date from git log output', () => {
expect(getProjectSince()).toEqual(new Date('2015-12-11T10:54:01.000Z'));
});

it('returns undefined if git log output is empty', () => {
vi.mocked(executeCommandSync).mockReturnValue('');

expect(getProjectSince()).toBeUndefined();
});

it('returns undefined if git log fails', () => {
vi.mocked(executeCommandSync).mockImplementation(() => {
throw new Error('git not available');
});

expect(getProjectSince()).toBeUndefined();
});
});
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

Consider adding a test case to verify that when git log returns multiple lines, only the first line is parsed. This would ensure the implementation correctly handles the typical multi-commit scenario. For example:

vi.mocked(executeCommandSync).mockReturnValue('2015-12-11 10:54:01 +0000\n2016-01-01 12:00:00 +0000');
expect(getProjectSince()).toEqual(new Date('2015-12-11T10:54:01.000Z'));

Copilot uses AI. Check for mistakes.
Comment on lines +129 to +147
describe('getProjectSince', () => {
it('returns the Storybook creation date from git log output', () => {
expect(getProjectSince()).toEqual(new Date('2015-12-11T10:54:01.000Z'));
});

it('returns undefined if git log output is empty', () => {
vi.mocked(executeCommandSync).mockReturnValue('');

expect(getProjectSince()).toBeUndefined();
});

it('returns undefined if git log fails', () => {
vi.mocked(executeCommandSync).mockImplementation(() => {
throw new Error('git not available');
});

expect(getProjectSince()).toBeUndefined();
});
});
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

Consider adding a test case for when git log returns an invalid date string to verify that the NaN check at line 69 properly handles this edge case. For example:

vi.mocked(executeCommandSync).mockReturnValue('not a valid date');
expect(getProjectSince()).toBeUndefined();

Copilot uses AI. Check for mistakes.
Comment thread code/core/src/telemetry/anonymous-id.ts
Copy link
Copy Markdown
Contributor

@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.

♻️ Duplicate comments (2)
code/core/src/telemetry/anonymous-id.test.ts (2)

137-139: Mock implementations still set inline within test cases.

All five sites set vi.mocked(executeCommandSync) behaviors inside individual it callbacks rather than in beforeEach. This violates the project's Vitest mocking guidelines.

Based on learnings: "Implement mock behaviors in beforeEach blocks in Vitest tests."

Also applies to: 145-145, 153-155, 172-172, 181-183

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/telemetry/anonymous-id.test.ts` around lines 137 - 139, Several
tests in anonymous-id.test.ts set vi.mocked(executeCommandSync) inside
individual it callbacks; move these mock implementations into a shared
beforeEach to follow Vitest guidelines. Create a beforeEach that calls
vi.mocked(executeCommandSync) and sets the appropriate return values (use
mockReturnValue or mockReturnValueOnce for sequences), include
vi.resetAllMocks() or vi.clearAllMocks() at the start of beforeEach to isolate
tests, and remove the inline vi.mocked(executeCommandSync)... calls from the it
blocks (adjust expected values in each test to rely on the beforeEach-provided
mock behavior). Ensure you reference the existing executeCommandSync mocked
symbol and use the same test case names so behavior remains consistent.

136-142: ⚠️ Potential issue | 🟡 Minor

Test 1 in each describe block will fail when tests run out of order due to memoized cache.

Both getProjectSince (lines 61–63, 79) and getAnonymousProjectId (lines 38–40, 51) use module-level caching. The static imports at the top of the test file (lines 5–10) are bound at module load time and are not reset by vi.resetModules() in beforeEach. This means:

  • Test 1 in each describe calls the statically-imported function whose cache persists across test runs
  • Tests 2 & 3 correctly use dynamic imports to fetch fresh module instances with empty caches
  • In watch mode, on retry, or when test order changes (e.g., .only), test 1 will return the stale cached value, causing failures

Apply the same vi.resetModules() + dynamic-import pattern to test 1 of each describe:

Fix for getProjectSince test 1 (lines 136–142)
 it('returns the Storybook creation date from git log output', () => {
   vi.mocked(executeCommandSync).mockReturnValue(
     '2025-12-11 16:24:01 +0530\n' + '2014-12-11 19:09:10 +0530'
   );

-  expect(getProjectSince()).toEqual(new Date('2025-12-11T10:54:01.000Z'));
+  const { getProjectSince: getProjSince } = await import('./anonymous-id');
+  expect(getProjSince()).toEqual(new Date('2025-12-11T10:54:01.000Z'));
 });
Fix for getAnonymousProjectId test 1 (lines 171–176)
 it('returns hashed project id for Storybook repo when git command succeeds', async () => {
   vi.mocked(executeCommandSync).mockReturnValue('git@github.com:storybookjs/storybook.git');
-  const result = getAnonymousProjectId();
+  const { getAnonymousProjectId: getAnonId } = await import('./anonymous-id');
+  const result = getAnonId();

   expect(result).toMatch('061e4ee22a1f7c079849d97234b3be94d016fb1f24ba11878c41f8b48c0213bf');
 });

Also move mock implementations from test bodies (lines 137–139, 145, 153–155, 172, 181–183) into beforeEach blocks and restore process.cwd spy (line 168) with vi.restoreAllMocks() in afterEach.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/telemetry/anonymous-id.test.ts` around lines 136 - 142, The
failing-first-test is caused by module-level memoized caches in getProjectSince
and getAnonymousProjectId persisting across tests; update the first test in each
describe to call vi.resetModules() and dynamically import the module (e.g.,
const { getProjectSince } = await import('...') / const { getAnonymousProjectId
} = await import('...')) so you get a fresh module instance, move
executeCommandSync and other mock implementations currently inside those test
bodies into the describe-level beforeEach to centralize mocking, and ensure
cleanup by calling vi.restoreAllMocks() (and vi.resetModules() as needed) in
afterEach so process.cwd spy and other mocks are restored between tests.
🧹 Nitpick comments (1)
code/core/src/telemetry/anonymous-id.test.ts (1)

171-171: Unnecessary async keyword — but will be needed after the dynamic-import fix.

The test body contains no await, making async redundant. However, once the static-import fix described above is applied (adding await import('./anonymous-id')), async will be required. No action needed if that fix is adopted.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/telemetry/anonymous-id.test.ts` at line 171, The test "returns
hashed project id for Storybook repo when git command succeeds" currently has an
unnecessary async on the it callback because there is no await; either remove
the async from that it(...) declaration to avoid misleading async usage, or if
you implement the earlier suggested dynamic import (await
import('./anonymous-id')), keep the async and add the awaited dynamic import
inside the test body so the async is required; locate the test by its
description string to apply the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@code/core/src/telemetry/anonymous-id.test.ts`:
- Around line 137-139: Several tests in anonymous-id.test.ts set
vi.mocked(executeCommandSync) inside individual it callbacks; move these mock
implementations into a shared beforeEach to follow Vitest guidelines. Create a
beforeEach that calls vi.mocked(executeCommandSync) and sets the appropriate
return values (use mockReturnValue or mockReturnValueOnce for sequences),
include vi.resetAllMocks() or vi.clearAllMocks() at the start of beforeEach to
isolate tests, and remove the inline vi.mocked(executeCommandSync)... calls from
the it blocks (adjust expected values in each test to rely on the
beforeEach-provided mock behavior). Ensure you reference the existing
executeCommandSync mocked symbol and use the same test case names so behavior
remains consistent.
- Around line 136-142: The failing-first-test is caused by module-level memoized
caches in getProjectSince and getAnonymousProjectId persisting across tests;
update the first test in each describe to call vi.resetModules() and dynamically
import the module (e.g., const { getProjectSince } = await import('...') / const
{ getAnonymousProjectId } = await import('...')) so you get a fresh module
instance, move executeCommandSync and other mock implementations currently
inside those test bodies into the describe-level beforeEach to centralize
mocking, and ensure cleanup by calling vi.restoreAllMocks() (and
vi.resetModules() as needed) in afterEach so process.cwd spy and other mocks are
restored between tests.

---

Nitpick comments:
In `@code/core/src/telemetry/anonymous-id.test.ts`:
- Line 171: The test "returns hashed project id for Storybook repo when git
command succeeds" currently has an unnecessary async on the it callback because
there is no await; either remove the async from that it(...) declaration to
avoid misleading async usage, or if you implement the earlier suggested dynamic
import (await import('./anonymous-id')), keep the async and add the awaited
dynamic import inside the test body so the async is required; locate the test by
its description string to apply the change.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 48b74f7 and 3052206.

📒 Files selected for processing (2)
  • code/core/src/telemetry/anonymous-id.test.ts
  • code/core/src/telemetry/anonymous-id.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • code/core/src/telemetry/anonymous-id.ts

@valentinpalkovic valentinpalkovic merged commit 036d55a into next Feb 24, 2026
122 of 125 checks passed
@valentinpalkovic valentinpalkovic deleted the shilman/add-project-since branch February 24, 2026 09:42
valentinpalkovic added a commit that referenced this pull request Feb 24, 2026
Telemetry: Add project age
(cherry picked from commit 036d55a)
@github-actions github-actions Bot added the patch:done Patch/release PRs already cherry-picked to main/release branch label Feb 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:normal maintenance User-facing maintenance tasks patch:done Patch/release PRs already cherry-picked to main/release branch patch:yes Bugfix & documentation PR that need to be picked to main branch telemetry

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants