Repository navigation
Conversation
Contributor
posthog-python Compliance ReportDate: 2026-10-08T23:45:52.274487+00:00 ✅ All Tests Passed!116/116 tests passed Capture_V1 Tests✅ 95/95 tests passed View Details
Feature_Flags Tests✅ 17/17 tests passed View Details
Feature_Flags_Local_Evaluation Tests✅ 4/4 tests passed View Details
|
3 of 20 tasks
eli-r-ph
force-pushed
the
v1-capture-options
branch
from
October 7, 2026 02:03
891b4c8 to
a515200
Compare
eli-r-ph
force-pushed
the
v1-capture-option-layers
branch
from
October 7, 2026 02:03
816a97e to
0f828c1
Compare
eli-r-ph
force-pushed
the
v1-capture-options
branch
from
October 7, 2026 17:43
a515200 to
ea1d279
Compare
eli-r-ph
force-pushed
the
v1-capture-option-layers
branch
from
October 7, 2026 17:43
0f828c1 to
4753ddb
Compare
Contributor
Author
|
@greptileai review |
eli-r-ph
marked this pull request as ready for review
October 7, 2026 20:43
eli-r-ph
force-pushed
the
v1-capture-options
branch
from
October 8, 2026 16:25
ea1d279 to
9705c81
Compare
eli-r-ph
force-pushed
the
v1-capture-option-layers
branch
from
October 8, 2026 16:25
4753ddb to
89a4dfb
Compare
PR overviewAll previously flagged issues have been addressed. No open security concerns remain on this pull request. Security reviewNo open security issues remain on this pull request. Fixed/addressed: 1 · PR risk: 0/10 |
Context tags, context options, super_properties, super_options, the derived personless option and the environment $release_id now fill only keys an event leaves unset, after before_send runs. before_send sees only the event's own values and SDK enrichment, and its changes beat every default. A None option counts as unset and is filled. Legacy properties still fill only unset options and are always removed.
Context tags, context options, super_properties, super_options, the derived personless option and the environment $release_id now fill before before_send runs, still only into keys the event leaves unset. before_send sees every value and has the final say, including removing a super property. $set, $set_once, $groups and $group_set fill one level deep when both values are dicts. The set() and set_once() values now win key by key over a $set or $set_once in properties, as ingestion merges them. Hoisting still runs once, after the hook.
$is_server, $geoip_disable and system context now fill last, only keys the event, context tags and super_properties left unset, before before_send. A super property $geoip_disable: False now wins over disable_geoip=True. The groups argument merges into a $groups property key by key and wins, in Client, AsyncClient and posthog.mcp events.
eli-r-ph
force-pushed
the
v1-capture-option-layers
branch
from
October 8, 2026 23:40
f595725 to
62ac4ab
Compare
eli-r-ph
force-pushed
the
v1-capture-options
branch
from
October 8, 2026 23:40
9705c81 to
965fbe6
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
💡 Motivation and Context
The previous PR in this stack added per-event
options. This PR adds the layers below them, so every capture option and property follows one order:options/propertiesset_context_option()/tag(), filling only what is still unsetsuper_options/super_properties, filling only what is still unset$is_server,$geoip_disable, system context such as$os, personless,$release_idfromPOSTHOG_RELEASE_ID), filling only what is still unsetbefore_send, which sees all of the above and has the final sayA property is unset when its key is missing. An option is unset when it is missing or
None.$set,$set_once,$groupsand$group_setfill one level deep when both values are dicts.posthog-go (PostHog/posthog-go#359) applies the same order. posthog-rs (PostHog/posthog-rs#279) has no context or global layer, and already removes legacy properties.
New API:
super_optionsonClient,AsyncPosthogand the module (posthog.super_options = {...}beforesetup()). It works the same way assuper_properties.set_context_option(key, value)/get_context_options()on the module,posthog.contextsandClient. They work the same way astag()/get_tags(): child contexts inherit them unlessfresh=True. Context options apply tocapture,capture_ai,capture_exception,setandset_once, the same paths that context tags reach.Behavior changes:
before_send.before_sendsees them, as in 7.x, and can change or remove them.$set,$set_once,$groupsand$group_setfill one level deep. In 7.x asuper_propertiesvalue replaced the event's whole dict. Now the event wins key by key.set()/set_once()values win over a$set/$set_onceinproperties. The v1 relocation used to let thepropertiescopy win, so a super$setbeat the call. Ingestion merges them with the call winning, and the relocation now does the same.Noneoption is filled by context, global and derived values.super_properties. Before,{**properties, **super_properties}let a global value overwrite the caller's per-event value. A side effect:super_propertiescan no longer override$libor$lib_version.$is_server,$geoip_disableand system context ($os,$python_versionand so on) fill last, aftersuper_properties, beforebefore_send. In 7.x the SDK overwrote an event's value for them, and$is_serveralso beatsuper_properties. Nowsuper_properties={"$geoip_disable": False}turns GeoIP on for events even withdisable_geoip=True. Flag requests still followdisable_geoip.groups=merges into a$groupsproperty and wins key by key, inClient,AsyncPosthogandposthog.mcpevents. It used to replace the property.options.process_person_profile = false, not the$process_person_profileproperty. Any per-event,before_send, context or global option overrides it.$process_person_profileproperty no longer overrides personless. The personless option is set, and an option beats its legacy property. To opt a personless event in, set the option.Not in this PR: AI wrappers and MCP moving their personless override to a per-event option (next PR).
💚 How did you test it?
before_send, sync, async andcapture_immediate: the hook sees context and global values, its change beats them, and it can remove a super property.$setand$groupsmerge with super$set/$groupskey by key,groups=wins over the event's$groupskey by key, and a list$unsetis not merged. An MCP identity's groups win over a custom$groupsthe same way.$is_server: False,$geoip_disable: Falseand$osbeat the SDK's values. Super values beat them on every capture path. The hook sees$is_server,$geoip_disableand$os, and can remove$is_server.set()/set_once()vs a super$set/$set_once: the call wins key by key. The_to_v1_eventunit test for the collision is flipped.Noneevent option is filled by a super optionsuper_optionson every sync method; context options oncapture,setandset_once.super_properties. The old test that pinned "super overrides$session_id" is inverted.$process_person_profile: true. A legacy super property still fills the option when nothing else sets it.fresh=Trueisolation, parent unchanged.setup()passessuper_optionsto the client.test_release_idand the minimal$feature_flag_calledtests read sent events at upload.$is_serveror$geoip_disable(sync and async), the system-context overwrite (sync and async), putting the SDK values abovesuper_properties, and thegroups=overwrite (sync, async and MCP) each fail the new tests. Moving the sync or async fill back after the hook, turning off the nested fill, and restoring the old relocation each fail the intended tests. Earlier, 12 reversions, each failed by the intended test:capture,set/set_onceand async capturesuper_propertieswinning again, sync and asyncsetup()droppingsuper_optionspython -W error -c "import posthog", the public API snapshot anduv lock --check.📝 Checklist
If releasing new changes
sampo addto generate a changeset fileCovered by the existing
capture-v1-majorchangeset; the migration guide lands later in this stack.🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Written with Cursor (Claude Opus) under the direction of the assignee.
Agreed before implementation:
super_optionsandset_context_option/get_context_optionsas the names. The fill beforebefore_send, the nested fill and the relocation fix were agreed for every v1 SDK.$is_server,$geoip_disable, system context, personless,$release_id) fill last, so every caller layer overrides themNoneoption counts as unset; a property key blocks the fill whatever its valueset()/set_once()keep merging context tags into$setAgent calls worth review:
$groups. The other built-in MCP properties keep their order.$process_person_profileinsuper_propertiesno longer opts a personless event in: the personless option is set, and an option beats its legacy property. Opting in takessuper_options={"process_person_profile": True}.AsyncPosthoghas noset_context_optionmethod; the module-level function works with it, astag()does today.group_identifyoralias, matching context tags.