Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Adding Activity Logging

ASecurity

Adds or changes activity logging (the audit trail) for a Django model in PostHog. Use when a model's writes must show in the Activity side panel or the advanced activity logs, when adding ModelActivityMixin, an ActivityScope, a model_activity_signal receiver, an activity describer, or field exclusions, when auditing which write paths of a model are logged, or when a change is missing from the activity log. Covers the receiver-module convention, writes the signal cannot see (QuerySet.update, b...

39,909 stars
0 votes
0 copies
2 views
Added 9/20/2026
developmentgoshellsqldjangodebuggingapidatabasefrontendbackend

Works with

api

Security Analysis

A100/100

Scanned 9/20/2026

Install to Claude Code

$npx -y skills add PostHog/posthog --skill adding-activity-logging --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Adding Activity Logging?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Adding Activity Logging
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/posthog-adding-activity-logging-posthog/badge)](https://www.skillsdirectory.com/skills/posthog-adding-activity-logging-posthog)

More formats (shields.io, HTML) on the badges page.

Download with Pro
Files
SKILL.md
---
name: adding-activity-logging
description: Adds or changes activity logging (the audit trail) for a Django model in PostHog. Use when a model's writes must show in the Activity side panel or the advanced activity logs, when adding ModelActivityMixin, an ActivityScope, a model_activity_signal receiver, an activity describer, or field exclusions, when auditing which write paths of a model are logged, or when a change is missing from the activity log. Covers the receiver-module convention, writes the signal cannot see (QuerySet.update, bulk_create), the actor outside requests, and product models on a separate database. Trigger terms - activity log, audit log, audit trail, ModelActivityMixin, log_activity, changes_between, activity describer, who changed this.
---

# Adding activity logging

Read [docs/internal/activity-logging.md](../../../docs/internal/activity-logging.md) first.
It carries the pipeline, the file locations, and the reasons.
This skill carries the workflow and the gates.

Reference implementation: `products/feature_flags/backend/activity_logging.py` with its `apps.py`.
Separate-database reference: `products/stamphog/backend/activity_logging.py`.

## Step 1 - Find every write path before you touch the model

The mixin hooks `save()` and `delete()`.
It does not see `QuerySet.update()`, `bulk_create()`, `bulk_update()`, or raw SQL.
A receiver that logs only the API path gives an audit trail that says nobody changed a row a webhook changed.

```sh
rg -n "Thing\.objects|Thing\.all_teams" products/<name>/ --type py | rg -v "tests/"
rg -n "\.update\(|bulk_create\(|bulk_update\(" products/<name>/backend --type py
```

Write the list down: which paths call `save()` (the mixin covers them) and which do bulk writes (you log those by hand in step 5).
Include tasks, Temporal activities, webhook handlers, and management commands.

## Step 2 - Model and scope

- Add `ModelActivityMixin` first in the bases: `class Thing(ModelActivityMixin, TeamScopedRootMixin, ...)`.
- Set `activity_logging_on_delete = True` only when a hard delete must show in the log.
- Add the class name to `ActivityScope` in `posthog/models/activity_logging/activity_log.py`.
- Add the same string to the `ActivityScope` enum in `frontend/src/types.ts`.
- Run `hogli build:openapi` (needs the dev stack) because the API `scope` filter enum is generated from the Literal.

No migration is needed.
The mixin adds no field.

## Step 3 - Receiver module

Create `products/<name>/backend/activity_logging.py`.
One `@mutable_receiver(model_activity_signal, sender=Thing)` per model.
The receiver calls `changes_between` and `log_activity`; nothing else.

Keep the module import-light.
It must not import a viewset, a serializer, or a query runner.
`ready()` imports it at `django.setup()` in every process.

For a model without a `.team` foreign key (`ProductTeamModel`), read `organization_id` from `Team` on the main database inside the receiver.

## Step 4 - Wire it

- In `products/<name>/backend/apps.py` `ready()`: `from products.<name>.backend import activity_logging  # noqa: F401, PLC0415`, with a one-line comment that says why it must connect in every process.
- Add `model_activity:products.<name>.backend.activity_logging.<handler>` to `posthog/test/repo_invariants/setup_receivers_baseline.txt`.

A receiver that is not imported in `ready()` connects only where something else imports the module.
That is the silent failure this convention exists to prevent.

## Step 5 - Bulk writes

For each bulk write from step 1:

- Read the before-values from the writer database before the update.
- Run the update.
- Build one `Change` per field that actually changed, for rows whose value changed.
- Call `bulk_log_activity` (or `log_activity` for one row) with `user=None` when no request user exists, and a `Trigger` that names the job and its id.

Do not wrap a request path in `mute_selected_signals()`.
The flag is process-wide.

## Step 6 - Separate-database products only

When the model is routed in `products/db_routing.yaml`:

- Pass `using=router.db_for_write(Thing)` to every `log_activity` and `bulk_log_activity` call in the product, including the receiver.
  Without it, the audit row is written before the product row commits and survives its rollback.
- The mixin already pins its before-update read to the writer and reads by primary key without a team filter.

## Step 7 - Tune the diff

- Fields a job touches on every run go in `signal_exclusions` (no row is logged when only they change).
- Bookkeeping fields go in `field_exclusions` (dropped from the diff).
- Secrets go in `field_with_masked_contents` (the change is recorded, the values are not).
- User-facing names go in `field_name_overrides`.

## Step 8 - Describer (optional)

`defaultDescriber` renders "X created / updated / deleted <name>".
Write `products/<name>/frontend/activityDescriber.tsx` and register it in `frontend/src/lib/components/ActivityLog/describers.tsx` when the default text does not say what changed in the user's words.
Invoke `/writing-user-facing-copy` before you write the strings.

## Step 9 - Tests

Invoke `/writing-tests` first.
Add the tests listed under Tests in the doc, and no more.

## Verify

```sh
hogli test products/<name>/backend/tests/<file>.py
hogli test posthog/test/repo_invariants/test_startup_import_budget.py
ruff check products/<name>/backend/activity_logging.py posthog/models/activity_logging --fix
```

Then check the row in a shell: `ActivityLog.objects.filter(scope="Thing").order_by("-created_at").first().detail`.

## Debugging a missing row

Work down this list; each item is a distinct cause.

1. The receiver module is not imported in `ready()` (check `setup_receivers_baseline.txt` for the handler name).
2. The write bypassed `save()` (step 1).
3. Only excluded fields changed (`signal_exclusions`), or the diff was empty and `force_save` was not set.
4. `ACTIVITY_LOG_TRANSACTION_MANAGEMENT` deferred the write to a commit that never came, or to the wrong connection (step 6).
5. The row exists but the reader is outside the entitlement's lookback window, or `activity_visibility_restrictions` hides it.

Attribution

PostHogPostHog
View sourceMore from PostHog →
SSkills DirectorySkills Directory

Your tool, in front of Claude Code builders.

3 founder slots · $299/mo · GSC-verified traffic · sponsors can never buy grades.

See placements

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Your tool, in front of Claude Code builders.

3 founder slots · $299/mo · GSC-verified traffic · sponsors can never buy grades.

See placements

Related Skills

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

284972 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2192 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Tanstack Start

Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per ...

10311 votes

Pentest

PTES-aligned adversarial security audit for backend, frontend, and mobile applications. Produces a CVSS-scored Hacker Report with verified PoCs and phased remediation.

5491 votes
View all in development →