Skip to content

feat(cli): add privacy-safe activity summaries - #116

Open
abouchard11 wants to merge 3 commits into
ActivityWatch:masterfrom
abouchard11:agent/privacy-safe-summary
Open

abouchard11 wants to merge 3 commits into
ActivityWatch:masterfrom
abouchard11:agent/privacy-safe-summary

Conversation

@abouchard11

Copy link
Copy Markdown

Summary

  • add aw-client summary for bounded, AFK-filtered category, application, and domain totals
  • aggregate on the local ActivityWatch server and emit either a table or provider-neutral JSON
  • make application/domain inclusion optional and document exactly which fields are included or omitted
  • reuse the active client when loading category settings so host, port, testing mode, and authentication stay consistent

Why

ActivityWatch's agent/AI guidance recommends locally aggregated, review-before-send context instead of raw exports. Today, users need to write the query and normalization code themselves. This command turns that workflow into a reproducible CLI path without exposing raw titles, full URLs, document names, message subjects, or raw event history.

Related: ActivityWatch/activitywatch#1388
Related: ActivityWatch/aw-webui#925

Validation

  • ruff check aw_client tests/test_summary.py
  • ruff format --check aw_client tests/test_summary.py
  • mypy aw_client tests/test_summary.py
  • XDG_DATA_HOME=/tmp/activitywatch-test-data pytest -q tests/test_summary.py tests/test_auth.py tests/test_requestqueue.py — 17 passed
  • real-server integration test against seeded window, AFK, and browser buckets; verified aggregates and confirmed confidential titles and full URL paths were absent from output

@greptile-apps

greptile-apps Bot commented Aug 17, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds a locally aggregated aw-client summary command with table and JSON output, configurable application/domain inclusion, and consistent category-settings lookup through the active client.

  • Builds bounded category, application, and domain aggregates on the ActivityWatch server.
  • Adds normalization, redaction metadata, human-readable formatting, documentation, and focused tests.
  • Extends category-settings loading to reuse the caller's configured client.

Confidence Score: 3/5

The PR should not merge until browser buckets with unknown hostname metadata can no longer disclose another machine's domain activity in a host-specific summary.

The new selector assigns every legacy unknown-host browser bucket to any requested hostname, so shared-server deployments can mix another machine's overlapping browser activity into privacy-oriented output.

Files Needing Attention: aw_client/summary.py and tests/test_summary.py

Security Review

The browser-bucket selector can associate legacy unknown-host buckets from other machines with the requested hostname, allowing overlapping domain activity to appear in a host-specific summary.

Important Files Changed

Filename Overview
aw_client/summary.py Adds bucket discovery and summary formatting, but unknown-host bucket inclusion can mix browser activity across machines.
aw_client/queries.py Adds bounded server-side category, application, and domain aggregation using existing canonical event semantics.
aw_client/cli.py Adds the summary command and consistently reuses the active client for category settings.
aw_client/classes.py Generalizes category-settings retrieval to accept a compatible caller-provided client while preserving fallback behavior.
tests/test_summary.py Covers aggregation, redaction, formatting, and CLI output, but codifies globally accepting unknown-host browser buckets.
README.md Documents valid summary invocations, output sensitivity, and application/domain omission controls.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    CLI[summary command] --> Buckets[Discover browser buckets]
    Buckets --> Query[Build privacySummary query]
    Settings[Load category settings through active client] --> Query
    Query --> Server[Local ActivityWatch aggregation]
    Server --> Normalize[Normalize bounded result]
    Normalize --> JSON[Provider-neutral JSON]
    Normalize --> Table[Human-readable table]
Loading

Reviews (1): Last reviewed commit: "feat(cli): add privacy-safe activity sum..." | Re-trigger Greptile

Comment thread aw_client/summary.py
@abouchard11
abouchard11 marked this pull request as ready for review August 25, 2026 18:41
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@ErikBjare

Copy link
Copy Markdown
Member

@TimeToBuildBob check this out

Comment thread tests/test_summary.py
Comment thread tests/test_summary.py
Comment thread aw_client/classes.py
@TimeToBuildBob

Copy link
Copy Markdown
Contributor

Greptile's concern is valid — wanted to flag the specific issue clearly.

In summary.py, find_browser_buckets() includes buckets where hostname is None, "", or "unknown":

if bucket_hostname not in (None, "", "unknown", hostname):
    continue

On a shared AW server (multiple machines reporting to one instance), a legacy browser bucket from machine B with no hostname metadata would be included in machine A's privacy-safe summary. That's a meaningful privacy leak for the exact use case this PR targets.

Simple fix: exclude unknown-host buckets by default and add an explicit --include-legacy-buckets flag for users who knowingly run a single-machine setup. The test in test_summary.py that asserts unknown-host buckets are globally accepted should become a test for the opt-in flag instead.

Everything else looks solid — the redaction policy, schema versioning, AFK filtering, and test coverage are all well-done.

find_browser_buckets() treated buckets reporting no hostname, an empty
hostname, or "unknown" as matching any host. On a server collecting from
several machines, a legacy browser bucket belonging to machine B was folded
into machine A's summary, leaking another host's browsing domains through a
command whose purpose is a privacy-safe summary.

Exclude unattributed buckets by default and add --include-legacy-buckets for
single-machine servers that want them counted. Record the choice in the
payload's redaction block so a consumer can tell which policy produced it.

The existing test asserted the leaking behaviour was correct; it now covers
the default exclusion, with a second test for the opt-in path.
@abouchard11
abouchard11 force-pushed the agent/privacy-safe-summary branch from dfdbd09 to 20cdf83 Compare August 25, 2026 19:16
@abouchard11

Copy link
Copy Markdown
Author

Good catch, thanks — fixed.

find_browser_buckets() now scopes to the requested hostname and excludes buckets with no hostname or unknown, since those can't be attributed to a machine. Added --include-legacy-buckets for single-machine servers that want them counted.

Updated the test that asserted unknown-host buckets were accepted, and added one for the opt-in path. Also recorded the choice in the redaction block as legacy_unknown_host_buckets — happy to drop that if you'd rather not change the schema.

Rebased on master to clear the conflict with #117.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor

Fix looks correct — now rejects unattributed hostnames by default and the include_legacy=True opt-in path is explicitly tested. The redaction block recording the policy choice is a nice touch too.

Copy link
Copy Markdown
Author

Review cleanup complete: the host-isolation finding is fixed on the current head (20cdf83), and the corrected default/opt-in behavior has been confirmed in review. I also documented and resolved the three CodeQL annotations: two are fixed-fixture assertions in redaction tests, and one is the conventional ellipsis body of a typing.Protocol declaration.

The Build, CodeQL, Examples, and Lint workflow runs currently show action_required with no jobs created, so they appear to be awaiting approval for workflows from the fork. @ErikBjare, when convenient, could you approve the runs so CI can execute? Thanks.

@abouchard11

Copy link
Copy Markdown
Author

Checked this against current master locally: #118 also touches cli.py, but the two merge cleanly and nothing regresses — 62 passed on the merge against 50 on master alone, with the same three pre-existing failures (test_auth, test_client, test_failqueue) that need a live aw-server either way.

The four workflows have been at action_required on 20cdf83 since Aug 25, so no checks have run on this head yet. @TimeToBuildBob could you approve the run when you get a chance? I've left the branch unrebased so your earlier review still applies to the current head, but happy to merge master in if you'd rather.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor

I can't approve these workflow runs: TimeToBuildBob only has read access to this repository, and GitHub rejects the rerun/approval action with 403 Must have admin rights to Repository. The four runs are still action_required with no jobs.

@ErikBjare this needs an aw-client maintainer/admin to approve the fork workflows. No rebase is needed right now; GitHub reports the current head mergeable, and keeping 20cdf83 preserves the reviewed SHA.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor

Rechecked the current state. The privacy issue is fixed on 20cdf83, the PR remains mergeable against current master, and the author has locally verified the merge with #118. The only unresolved gate is still GitHub's fork-workflow approval: all four current-head runs are action_required, and I reconfirmed that my approval attempt returns 403 Must have admin rights to Repository.

@ErikBjare please approve the four workflow runs when convenient. No author change or rebase is needed before that.

The Lint workflow runs psf/black@stable and flagged both files added by
this branch. Formatting only, no behaviour change.
@TimeToBuildBob

Copy link
Copy Markdown
Contributor

Head moved to 5c0013cd after Lint failed on 20cdf83. That failure was psf/black --check on aw_client/summary.py and tests/test_summary.py (extra blank lines plus one wrapped call). The new commit is exactly that black diff — formatting only, no behavior change.

The four current-head workflows are action_required again and have not started jobs:

I still cannot approve them: TimeToBuildBob has pull-only access, and POST .../actions/runs/34066540100/approve returns 403 Must have admin rights to Repository.

@ErikBjare please click Approve and run workflows on those four runs. After that, Lint is the check that should go green; Build/CodeQL/Examples already passed on 20cdf83.

@abouchard11

Copy link
Copy Markdown
Author

@ErikBjare the four CI runs here have been waiting on maintainer approval since Sept 6; fork runs need that click. Everything raised in review is addressed and TimeToBuildBob has re-verified. Is there anything else you need from me to get this in?

@TimeToBuildBob TimeToBuildBob left a comment

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.

Re-verified on 5c0013c after fork workflows ran. Lint, Build (ubuntu/macOS/windows), Examples, and CodeQL Analyze (python) are green. Local merge with current master including #118 is clean; 12 summary tests plus the profile/queue suite pass on the merge. Host-isolation default is still correct. The remaining GitHub Advanced Security CodeQL check is the same three documented false positives (two test assertions for domain-only github.com, one typing.Protocol ellipsis).

@TimeToBuildBob

Copy link
Copy Markdown
Contributor

Nothing else needed from you.

Fork workflows have now run on 5c0013cd. Lint, Build (ubuntu/macOS/windows), Examples, and CodeQL Analyze (python) are green. I also merged this branch onto current master locally (includes #118): clean merge, --profile and the --port override fix are preserved, and tests/test_summary.py plus the profile/queue suite pass.

The remaining red CodeQL check is GitHub Advanced Security, not the Actions workflow — same three annotations you already documented as false positives (two github.com test assertions, one typing.Protocol ellipsis). No rebase needed; a rebase would just restart fork-workflow approval.

I submitted an approve review. I still cannot merge (TimeToBuildBob is pull-only). @ErikBjare this is ready to squash-merge; the leftover CodeQL check can be dismissed as the documented false positives.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants