Skip to content

feat(profile): named instance profiles via --profile flag - #652

Merged
ErikBjare merged 10 commits into
ActivityWatch:masterfrom
TimeToBuildBob:feat/profile
Aug 31, 2026
Merged

feat(profile): named instance profiles via --profile flag#652
ErikBjare merged 10 commits into
ActivityWatch:masterfrom
TimeToBuildBob:feat/profile

Conversation

@TimeToBuildBob

@TimeToBuildBob TimeToBuildBob commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Part of ActivityWatch/activitywatch#1399.

What

Named --profile on aw-server-rust, with --testing as an alias for --profile testing. Replaces static mut TESTING with OnceLock<String> PROFILE.

Testing-root fallback (activitywatch#1399 / aw-core#152)

Identical contract to python. Resolution rule:

  1. If activitywatch-testing/ already exists → use it.
  2. Else if legacy testing artifacts exist in the bare activitywatch/ root (sqlite-testing.db, config-testing.toml, python peewee-sqlite-testing*.db, …) → stay in legacy mode (old paths, old filenames).
  3. Else (fresh setup) → create and use activitywatch-testing/.

Inside isolated profile roots (including new-style testing), filenames are bare: sqlite.db, config.toml, unsuffixed logs. Suffixed names remain only in legacy mode so existing sqlite-testing.db files are not orphaned.

set_profile() now runs before setup_logger(), so named profiles log into their own cache dir rather than the shared one.

AW_PROFILE is the env fallback when --profile is absent (matches aw-qt exporting the env to children). CLI flag wins.

Developer-mode

is_testing() is still profile == "testing" only. A research instance is production-mode (no Rocket debug / permissive CORS).

Tests

Fallback rule covered against fake XDG roots: fresh / legacy artifacts / new-root-wins / config-testing.toml marker / named profiles stay isolated.

@greptile-apps

greptile-apps Bot commented Aug 23, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds named server and sync profiles, including CLI/environment resolution and isolated configuration, database, and log roots while preserving legacy testing paths.

  • Replaces mutable testing state with an immutable process-wide profile.
  • Adds profile-aware filesystem resolution and testing-root compatibility fallback.
  • Exposes the active profile through server information and propagates profile selection to aw-sync.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
aw-server/src/config.rs Replaces mutable testing state with atomic profile initialization; the updated conflict handling rejects differing concurrent or compatibility-setter values.
aw-server/src/dirs.rs Adds profile-aware platform roots, legacy testing detection, isolated filenames, and profile validation.
aw-server/src/main.rs Resolves and initializes the profile before logging, configuration loading, and datastore selection.
aw-server/src/logging.rs Routes logs by profile while retaining legacy testing filenames only for shared-root installations.
aw-sync/src/dirs.rs Aligns sync and embedded-server configuration paths with the selected server profile.
aw-sync/src/main.rs Adds profile selection and initializes the shared profile before resolving local server settings.
aw-models/src/info.rs Adds a backward-compatible profile field to the server information model.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[CLI --profile] --> D[Resolve profile]
    B[AW_PROFILE] --> D
    C[--testing alias or build default] --> D
    D --> E[Validate and initialize OnceLock]
    E --> F[Select profile config root]
    E --> G[Select profile database root]
    E --> H[Select profile log root]
    E --> I[Expose profile through server info]
    E --> J[Propagate profile to aw-sync]
    F --> K{Testing profile?}
    G --> K
    H --> K
    K -->|New root exists| L[Use isolated activitywatch-testing root]
    K -->|Legacy artifacts exist| M[Use shared root with testing suffixes]
    K -->|Fresh setup| L
Loading

Reviews (6): Last reviewed commit: "fix(aw-sync): keep Android filesDir conf..." | Re-trigger Greptile

Comment thread aw-server/src/config.rs
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Fixed two CI failures:

Clippy (aw-sync/src/main.rs:156): setup_logger signature changed from testing: bool to profile: &str, but the call site in aw-sync wasn't updated. Fixed by converting opts.testing to a profile string:

let profile = if opts.testing { "testing" } else { "default" };
aw_server::logging::setup_logger("aw-sync", profile, verbose)?;

Format (aw-server/src/dirs.rs): Long assert! lines in tests exceeded rustfmt's line width. Ran cargo fmt to reformat them.

Push: e7719a2

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

@greptileai review

Comment thread aw-server/src/config.rs
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

@greptileai review

@ErikBjare

Copy link
Copy Markdown
Member

@TimeToBuildBob fix broken CI

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Confirmed. The four red jobs all fail at the same integration assertion: aw-client-rust/tests/test.rs:202 expects info.testing, but the test server now reports false after the profile migration. Android, format, and clippy are green. I am fixing the testing-profile propagation and will post the focused SHA/test evidence here.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Fixed in aecb912: the in-process test server fixture now explicitly sets testing: true, so /api/0/info matches the integration test contract after the profile refactor. Reproduced the failure first, then verified cargo test -p aw-client-rust --test test (2 passed) and cargo fmt --all -- --check. The new CI run is in progress.

@codecov

codecov Bot commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 70.10309% with 58 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.26%. Comparing base (656f3c9) to head (aae79f8).
⚠️ Report is 97 commits behind head on master.

Files with missing lines Patch % Lines
aw-server/src/main.rs 0.00% 15 Missing ⚠️
aw-sync/src/main.rs 0.00% 15 Missing ⚠️
aw-server/src/dirs.rs 90.43% 11 Missing ⚠️
aw-sync/src/dirs.rs 72.00% 7 Missing ⚠️
aw-server/src/config.rs 72.22% 5 Missing ⚠️
aw-server/src/logging.rs 0.00% 3 Missing ⚠️
aw-models/src/info.rs 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #652      +/-   ##
==========================================
+ Coverage   70.81%   79.26%   +8.44%     
==========================================
  Files          51       67      +16     
  Lines        2916     5768    +2852     
==========================================
+ Hits         2065     4572    +2507     
- Misses        851     1196     +345     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

CI-green and mergeable (Greptile 5/5) — waiting only on a maintainer click.

This PR is ready to merge, but the bot has pull-only access to this repo and can't self-merge — surfacing it here so it isn't lost. The monitoring loop will stop re-flagging it now that this note is posted.

@ErikBjare

Copy link
Copy Markdown
Member

@TimeToBuildBob This is not the approach we've discussed

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

You're right, and I see exactly what's wrong. This PR still uses the old filename-suffix approach — sqlite.db, sqlite-testing.db in the same directory — while aw-core#149 was revised to the per-profile appname approach you proposed: fully separate directory roots (activitywatch-testing/, activitywatch-research/) so cleanup is rm -rf ~/.local/share/activitywatch-research/ and nothing else.

I'll revise this PR to match: when profile is non-default, the app name passed to the app_dirs / dirs crate becomes activitywatch-<profile>, giving each profile its own isolated data/config tree. Same mechanism as aw-core#149. The filename logic in dirs.rs becomes unnecessary and can be dropped entirely.

One question before I push: should the Rust side pick up the AW_PROFILE env var (matching how aw-core sets the appname), or is --profile NAME on the CLI the sole setter, with no env fallback?

@ErikBjare

Copy link
Copy Markdown
Member

@TimeToBuildBob Probably env fallback? What does aw-core do?

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

aw-core reads os.environ.get("AW_PROFILE") in _get_appname() — env var only, no CLI flag at that layer. The planned flow per the RFC is aw-qt sets AW_PROFILE=<name> before spawning children, so each child inherits it without needing an explicit flag.

For aw-server-rust that means: --profile NAME on the CLI is the override; AW_PROFILE is the fallback. I'll implement it that way in the revision — read env, override with CLI flag if supplied, validate whichever wins.

@ErikBjare

Copy link
Copy Markdown
Member

@TimeToBuildBob fix it

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Revised in three commits (d7f8db2..7d75747):

  1. Per-profile directory root (d7f8db2): non-default/non-testing profiles now use a sibling appname (activitywatch-research, activitywatch-mywork, etc.) so config, data, cache, and logs are fully isolated. default and testing keep the bare activitywatch root — no migration needed. The filename-suffix approach (sqlite-testing.db) is dropped from dirs.rs.

  2. AW_PROFILE env fallback (791cff9): --profile NAME overrides; AW_PROFILE is the fallback (matches how aw-qt propagates the profile to child processes without requiring every module to grow a flag). CLI flag wins when both are set.

  3. aw-sync isolation (7d75747): aw-sync picks up the profile from aw_server::config::get_profile() so its config dir goes under the same activitywatch-<profile> root rather than a separate path.

Result: rm -rf ~/.local/share/activitywatch-research/ cleanly removes a research instance with zero spillover.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

@greptileai review

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Testing-root fallback is in 4e555f2, matching ActivityWatch/aw-core#152 / activitywatch#1399.

Rule (identical to python):

  1. activitywatch-testing/ exists → use it
  2. else legacy testing artifacts in the bare activitywatch/ root → stay legacy (sqlite-testing.db, config-testing.toml)
  3. else fresh → create and use activitywatch-testing/

Bare filenames inside isolated profile roots. set_profile() now runs before setup_logger() so named profiles no longer log into the shared cache dir.

Local: cargo test -p aw-server -p aw-sync green (39+10 unit tests + integration). CI running on the new head.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

@greptileai review

Replace the two-valued `testing: bool` with a named profile string so that
more than two parallel ActivityWatch instances can coexist on one machine.

- `dirs.rs`: `db_path(profile)` → `sqlite.db` / `sqlite-<profile>.db`; add
  `validate_profile()` (lowercase alnum + `-_`, max 32 chars, starts with
  alnum); tests for suffix rule and validation
- `config.rs`: replace `static mut TESTING: bool` with `OnceLock<String>
  PROFILE`; `set_profile()` is idempotent for same value, panics on
  conflict; `get_profile()` / `is_testing()` derived from it; config file
  is `config.toml` / `config-<profile>.toml`
- `logging.rs`: `setup_logger(module, profile, verbose)` — logfile suffix
  is `<module>-<profile>_<ts>.log` for non-default profiles
- `main.rs`: add `--profile NAME`; `--testing` remains as alias for
  `--profile testing`; debug builds still default to "testing"; profile is
  validated before use
- `android/mod.rs`: update two call-sites to pass `"default"`

Backwards compatibility:
- `--testing` still works (alias for `--profile testing`)
- `default` profile maps to existing unsuffixed paths (sqlite.db,
  config.toml) — no migration required
- `testing` profile maps to existing -testing suffix paths

Part of ActivityWatch/activitywatch#1399.
setup_logger signature changed to accept profile: &str instead of testing: bool.
Convert opts.testing bool to "testing"/"default" profile string at the call site.

Also run cargo fmt to fix long assert! lines in dirs.rs tests.
…::set result

Previously the function checked PROFILE.get() then PROFILE.set() in two separate
steps. Two threads with different profile values could both see get()==None before
either set, causing the loser's set() error to be discarded and the loser to
silently proceed under the wrong profile.

Fix: use the atomic OnceLock::set return value directly. If Ok(()), we won the
race. If Err(_), the lock was already set by a concurrent caller; check the existing
value and panic only if it differs.
…ct panics

Greptile P1: bare PROFILE.set() in set_testing silently discarded conflicts
even when the existing profile differed, allowing a losing caller to proceed
under the wrong instance. Delegating to set_profile() reuses its idempotent
same-value check and conflict panic, matching the documented semantics.
…rver info

--profile only suffixed the DB filename, so a non-default instance still
shared config.toml, the cache dir and the log dir with prod — the one thing
profile isolation is for.

Move the profile into the platformdirs appname instead
("activitywatch-<profile>"), which isolates config/data/cache/logs and
everything nested under them in one place, with no per-module path changes.

default and testing deliberately keep the bare "activitywatch" root: their
legacy per-file suffixes (sqlite-testing.db, config-testing.toml, port 5666)
already separate them, and moving their root would orphan existing installs.

Also add Info.profile so clients (webui badge) can tell concurrent instances
apart; it deserializes with a "default" fallback so a new client still parses
an older server's /api/0/info.
aw-qt exports AW_PROFILE for the modules it spawns (ActivityWatch/aw-qt#128),
so a profile set on the launcher reaches aw-server-rust without every module
growing its own flag. --profile still wins when given.
aw-sync hard-coded activitywatch/aw-sync, so a research instance would
read prod's sync config. Use aw_server::dirs::appname() and the same
config-{profile}.toml filename rule as the server.

aw-sync now resolves --profile / AW_PROFILE / --testing and calls
set_profile so appname() is the named profile, not always default.
Adopt the activitywatch#1399 rule so rust matches aw-core#152:

1. activitywatch-testing/ exists → use it
2. else legacy testing files in activitywatch/ → stay on the shared root
   (sqlite-testing.db, config-testing.toml)
3. else fresh setup → create and use activitywatch-testing/

Isolated profile roots use bare sqlite.db / config.toml / log names.
set_profile now runs before setup_logger so named profiles log into
their own cache dir.
Master's ActivityWatch#666 reads the embedded server's config from filesDir for
the API key. After rebasing onto that, get_server_config_path must
not switch Android onto desktop XDG + appname_for (those helpers
are cfg-gated off Android). Desktop still uses the isolated
activitywatch-<profile> root.
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Rebased onto current master (2ded7d7) to clear the DIRTY/CONFLICTING state.

Conflict was in aw-sync/src/dirs.rs against #666 (Android API-key / filesDir lookup). Resolution kept both sides:

  • master's Android files_dir_from_xdg_data_home helper + tests
  • this PR's --profile / AW_PROFILE isolation

Follow-up aae79f8: get_server_config_path stays on filesDir on Android (appname_for is desktop-only). Desktop still uses the isolated activitywatch-<profile> root.

Local verify: cargo test -p aw-server --lib (43 passed), cargo test -p aw-sync --lib (13 passed), cargo test -p aw-client-rust --test test (2 passed), cargo fmt --all -- --check.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

@greptileai review

@ErikBjare
ErikBjare merged commit c8533a0 into ActivityWatch:master Aug 31, 2026
8 checks passed
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Merged — thanks. The final head uses per-profile directory roots, AW_PROFILE fallback with CLI override, the Python-compatible testing-root fallback, and preserves Android filesDir behavior from #666. CI and Greptile were green on that head.

TimeToBuildBob added a commit to TimeToBuildBob/aw-webui that referenced this pull request Sep 3, 2026
… absence

Since ActivityWatch/aw-server-rust#652, --testing servers log to
~/.cache/activitywatch-testing/ while released builds use
~/.cache/activitywatch/. Use shopt nullglob to gracefully handle
both paths and avoid failing when no log files are present.

Git-Session-Id: 8fb4
ErikBjare pushed a commit to ActivityWatch/aw-webui that referenced this pull request Sep 3, 2026
… absence (#962)

The 'aw-server-rust master' matrix leg has failed on every PR since the
aw-server-rust nightly picked up ActivityWatch/aw-server-rust#652 (named
instance profiles, merged 2026-08-31). That change resolves the cache root
via appname(): a --testing server on a machine with no legacy testing data
now logs under ~/.cache/activitywatch-testing/, not ~/.cache/activitywatch/.

GitHub runners are fresh, so the nightly leg writes to the new root while the
pinned v0.12.3b18 legs (pre-#652 binaries) still use the old one - exactly the
observed pass/fail split.

Glob both roots so the diagnostics come back, and guard with nullglob so these
always()-gated log steps can no longer fail the job when no logs exist. A
diagnostic step should never gate a merge.

Git-Session-Id: f31d
ironcatan pushed a commit to ironcatan/aw-webui-es that referenced this pull request Sep 5, 2026
… absence (#962)

The 'aw-server-rust master' matrix leg has failed on every PR since the
aw-server-rust nightly picked up ActivityWatch/aw-server-rust#652 (named
instance profiles, merged 2026-08-31). That change resolves the cache root
via appname(): a --testing server on a machine with no legacy testing data
now logs under ~/.cache/activitywatch-testing/, not ~/.cache/activitywatch/.

GitHub runners are fresh, so the nightly leg writes to the new root while the
pinned v0.12.3b18 legs (pre-#652 binaries) still use the old one - exactly the
observed pass/fail split.

Glob both roots so the diagnostics come back, and guard with nullglob so these
always()-gated log steps can no longer fail the job when no logs exist. A
diagnostic step should never gate a merge.

Git-Session-Id: f31d
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.

2 participants