feat: sync personalization from user-level AGENTS.md/CLAUDE.md#3286
Conversation
|
Merging to
After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here |
|
React Doctor found 1 issue in 1 file · 1 warning. 1 warning
Reviewed by React Doctor for commit |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 139b406594
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
pauldambra
left a comment
There was a problem hiding this comment.
Note
🤖 Automated comment by QA Swarm — not written by a human
QA Swarm review complete. See inline comments.
| }); | ||
| } | ||
|
|
||
| private async reconcile(enabled: boolean): Promise<void> { |
There was a problem hiding this comment.
Note
🤖 Automated comment by QA Swarm — not written by a human
[paul] 🟢 LOW
how do we find out when this quietly does nothing? the toggle fires SETTING_CHANGED, nice — but the states that actually matter are invisible: "sync on, no file found" and "truncated a big file". since this changes what gets injected into every session, i wonder if reconcile should emit a small analytics ping on the outcome (found / not-found / truncated, maybe which of the four candidates won). that's what tells us whether people land on AGENTS.md or silently get nothing. the log.warn on read failure keeping the last snapshot is exactly right though — no notes there. not blocking, just a "log before you limit" itch.
|
Note 🤖 Automated comment by QA Swarm — not written by a human Multi-perspective review: paul-reviewer, xp-reviewer, security-audit (qa-team unavailable in this environment — skipped) Verdict: 💬 APPROVE WITH NITSNo correctness or security blockers found; two MEDIUM design/observability questions plus a handful of nits, all non-blocking. Key findingsMEDIUM
LOW
NIT — Windows ConvergenceNo two reviewers flagged the same line independently. Adjacent concerns: paul's cloud-cap question and xp's cap-constant triplication both orbit the 20k limit — highest-confidence area to tighten. Reviewer summaries
Automated by QA Swarm — not a human review |
Adds a toggle on the Personalization settings tab that mirrors the user's user-level agent instructions file into custom instructions, so the same guidance doesn't have to be maintained in two places. - workspace-server: os.getUserAgentInstructions() probes ~/.agents/AGENTS.md, ~/.codex/AGENTS.md, ~/.claude/AGENTS.md, then ~/.claude/CLAUDE.md and returns the first non-empty file (truncated at 20k chars). - ui: persisted syncCustomInstructionsFromFile setting, runtime synced snapshot, and a boot contribution that re-reads the file once settings hydrate and whenever the toggle flips. - Sessions (local system prompt) and cloud task creation both use the synced content when available, falling back to the hand-typed instructions. - Agent session schemas accept the larger synced payload (20k cap, up from 2k). Generated-By: PostHog Code Task-Id: b6359217-b9b8-40bb-a748-308fa450a732
Make the sync semantics strictly either/or: while the toggle is on, only the synced AGENTS.md/CLAUDE.md content applies (nothing when no file is found) and the hand-typed instructions are ignored. The personalization textarea is now disabled and greyed out whenever sync is on, keeping the typed draft visible but clearly inactive, with a caption naming the file in use or a warning when none was found. Generated-By: PostHog Code Task-Id: b6359217-b9b8-40bb-a748-308fa450a732
Split PersonalizationSettings into a pure PersonalizationSettingsView (props only) and the store-wired container, per the storybook-stories convention, and add stories for each visual state: editable box (empty and filled), synced from CLAUDE.md/AGENTS.md with the box disabled, truncated file, and sync on with no file found. Also drop the disabled box to 50% opacity — Radix's disabled state alone is too subtle in dark mode to read as inactive. Generated-By: PostHog Code Task-Id: b6359217-b9b8-40bb-a748-308fa450a732
Temporary assets so the PR description can embed the story states; removed again in the next commit — the raw URLs stay pinned to this commit's SHA. Generated-By: PostHog Code Task-Id: b6359217-b9b8-40bb-a748-308fa450a732
The PR description references them via the previous commit's pinned raw URLs; the working tree stays clean. Generated-By: PostHog Code Task-Id: b6359217-b9b8-40bb-a748-308fa450a732
The preview's Theme used radius="none" and swapped accent colors (orange in dark, yellow in light), so stories rendered square toggles in the wrong accent. Mirror ThemeWrapper: radius="medium", yellow in dark, orange in light. Generated-By: PostHog Code Task-Id: b6359217-b9b8-40bb-a748-308fa450a732
Temporary assets for the PR description; removed in the next commit — the raw URLs stay pinned to this commit's SHA. Generated-By: PostHog Code Task-Id: b6359217-b9b8-40bb-a748-308fa450a732
Generated-By: PostHog Code Task-Id: b6359217-b9b8-40bb-a748-308fa450a732
React Doctor flagged the store-value-copied-into-state + mirror-effect pattern (no-derived-state). Replace it with a nullable draft that renders over the store value only while edits are pending: typing sets the draft, the debounce (or blur) persists it and releases it back to the store value, so external store changes render without an effect and can no longer clobber in-progress typing. Generated-By: PostHog Code Task-Id: b6359217-b9b8-40bb-a748-308fa450a732
…oggle re-enable Re-enabling "sync custom instructions from file" left the previous snapshot in the store while the new file read was in flight. A session created in that window (e.g. right after turning sync off, editing the file, then back on) would inject the stale prior snapshot instead of nothing. Clear the snapshot synchronously before the async read so the gap resolves to empty personalization rather than stale content. Generated-By: PostHog Code Task-Id: b6359217-b9b8-40bb-a748-308fa450a732
…nstant The 20,000-char cap was asserted three times with a copy-pasted comment: OsService's truncation plus the .max(20_000) on both startSessionInput and reconnectSessionInput. Bumping one without the others would either truncate silently below the intended length or reject a synced instructions file that was truncated to fit. Export USER_AGENT_INSTRUCTIONS_MAX_LENGTH from os/schemas.ts, alongside the UserAgentInstructions type it bounds, and reference it from all three sites. Generated-By: PostHog Code Task-Id: b6359217-b9b8-40bb-a748-308fa450a732
…tInstructions The existing "prefers an AGENTS.md over the user CLAUDE.md" case seeded .codex/AGENTS.md + .claude/CLAUDE.md, so it never exercised the first candidate (.agents/AGENTS.md) or pinned the .agents-before-.codex ordering — a reorder of USER_AGENT_INSTRUCTIONS_CANDIDATES swapping those two entries would still pass. Add a case seeding both so the ordering is covered. Generated-By: PostHog Code Task-Id: b6359217-b9b8-40bb-a748-308fa450a732
Dedupe the customInstructions Zod field shared by startSessionInput and reconnectSessionInput into one const, and flatten the hydrate/toggle detection in the sync contribution's store subscription. Generated-By: PostHog Code Task-Id: b6359217-b9b8-40bb-a748-308fa450a732
After the simplify pass moved the state._hasHydrated guard out of the expression, `becameHydrated = !prev._hasHydrated` standing alone is true for any change while un-hydrated — the name only told the truth inside the combined condition. `wasUnhydrated` is self-contained. Generated-By: PostHog Code Task-Id: b6359217-b9b8-40bb-a748-308fa450a732
Two reads in flight at once (rapid toggle off/on/off/on) could resolve out of order, letting the stale read land last and win while sync is on. A late read resolving after toggle-off could likewise deposit a latent snapshot. Capture a generation before the await and discard any read a newer reconcile has superseded. The read is a host round-trip, so on slower transports than local IPC the window is real. Covers the contribution with tests: happy path, clear-on-disable, and both race orderings (verified the race tests fail without the guard). Generated-By: PostHog Code Task-Id: b6359217-b9b8-40bb-a748-308fa450a732
The three getUserAgentInstructions preference tests shared one shape — givenFiles in, expected winner out — matching the repo convention of parameterising same-logic cases (as the getEffectiveCustomInstructions table already does). Coverage is unchanged. Generated-By: PostHog Code Task-Id: b6359217-b9b8-40bb-a748-308fa450a732
Correct the flush helper's comment (setTimeout(0) waits a macrotask turn, not microtasks) and replace hand-rolled promise-capture boilerplate with the repo's deferred<T>() test idiom. Test-only; the generation-guard pins were mutation-checked before and after. Generated-By: PostHog Code Task-Id: b6359217-b9b8-40bb-a748-308fa450a732
The "clear first, so a rejected read leaves personalization empty rather than reviving stale content" decision lived only in a comment. Seed a leftover snapshot, reject the read, and assert the store ends null — a refactor reverting to keep-last-snapshot now fails this test (verified by removing the pre-clear). Generated-By: PostHog Code Task-Id: b6359217-b9b8-40bb-a748-308fa450a732
68cf812 to
f1965a5
Compare
There was a problem hiding this comment.
This T1d-complex feature touches session instruction passing, raises schema limits 10×, and adds persisted settings — but every review is from an automated bot with no human sign-off. There is also an unresolved MEDIUM concern about the cloud task creation path: synced instructions up to 20,000 chars flow into the cloud task API with no client-side length guard, while the Django backend may still enforce the old 2,000-char limit, producing failures for users with large AGENTS.md files who create cloud tasks.
…s-md Generated-By: PostHog Code Task-Id: 7bc77008-3d23-4db7-bcf3-3320960dd522
Merge activity
|
richardsolomou
left a comment
There was a problem hiding this comment.
@pauldambra roborich got to reviewing this a bit after it merged, but there might be some good follow-ups here
| // unsubscribes, so one shared instance serves every test. | ||
| let started = false; | ||
|
|
||
| beforeEach(() => { |
There was a problem hiding this comment.
could use a test for the boot path here, its the headline behaviour and the suite never runs it. beforeEach pre-sets _hasHydrated: true, so the false -> true transition (the actual production boot order, persist rehydration flips it async after start()) never happens here. delete the if (initial._hasHydrated) block and shrink the subscribe guard to just toggleFlipped and all 5 tests stay green - but anyone with sync persisted-on gets no file read at startup until they re-toggle. a test that starts the contribution unhydrated with sync on, flips hydration true, and asserts the read fires would pin it.
| const state = useSettingsStore.getState(); | ||
| return { | ||
| ...state, | ||
| customInstructions: getEffectiveCustomInstructions(state), |
There was a problem hiding this comment.
nothing asserts synced content actually reaches session start - both sessionServiceHost test files mock the store without the new fields, so getEffectiveCustomInstructions only ever takes its pass-through branch here (same for the cloud path at packages/ui/src/features/task-detail/hooks/useTaskCreation.ts:333). reverting this line to state.customInstructions kills the feature for every local session and passes green. worth an assert that sync on + a snapshot lands the file content in the startSession payload?
| ); | ||
| }); | ||
|
|
||
| describe("getEffectiveCustomInstructions", () => { |
There was a problem hiding this comment.
would it be worth pinning that syncedCustomInstructions stays out of partialize (settingsStore.ts:478 has the toggle, correctly not the snapshot)? nothing asserts it today, so someone adding it later passes green - and then a stale snapshot rehydrates at boot and can hit a session created before the contribution's clear-and-re-read finishes, exactly the stale-snapshot case the contribution documents. one test covering toggle-persisted + snapshot-not-persisted would close it.
Follow-up to #3286. Close three test-coverage gaps where the AGENTS.md/CLAUDE.md sync feature could silently break while the suite stayed green: - customInstructionsSync.contribution: assert the file read fires on the unhydrated -> hydrated boot transition when sync is persisted-on (the headline boot path the suite never exercised). - sessionServiceHost: give the store mock the sync fields and assert the synced file content (not the hand-typed instructions) reaches the agent.start payload. - settingsStore: assert the sync toggle persists but the runtime snapshot stays out of partialize, so a stale snapshot can't rehydrate. Each test was mutation-checked to confirm it fails on the corresponding regression. Generated-By: PostHog Code Task-Id: 989ab6ef-4d6c-4e95-912c-105f9e842b31
Problem
If you keep personal agent instructions in a user-level AGENTS.md or CLAUDE.md, you have to copy the same content into PostHog Code's Personalization tab and keep the two in sync by hand.
Refs #3251
Changes
Adds a Sync from AGENTS.md / CLAUDE.md toggle above the custom-instructions text box on the Personalization tab.
~/.agents/AGENTS.md,~/.codex/AGENTS.md,~/.claude/AGENTS.md, then~/.claude/CLAUDE.md— AGENTS.md wins when both exist.workspace-server(os.getUserAgentInstructions, capped at 20k chars), forwarded by the host router, and driven by a settings boot contribution; session schemas now accept the larger synced payload.PersonalizationSettingsViewwith Storybook stories for each state (Settings/PersonalizationSettings). The Storybook preview theme was also fixed to mirror the app's ThemeWrapper (radius="medium", yellow accent in dark) — it previously rendered square toggles in the wrong accent.Screenshots (from the Storybook stories)
How did you test this?
OsService.getUserAgentInstructions(precedence, fallback, empty-file skip, truncation) andgetEffectiveCustomInstructions(sync on/off, missing/whitespace file).pnpm --filtertypecheck for workspace-server, host-router, ui, and the Electron app; Biome check on touched files;node scripts/check-host-boundaries.mjs.Automatic notifications
Created with PostHog Code