Skip to content

feat: a herdr pane says which Claude account its agent is running as - #573

Merged
blooop merged 5 commits into
mainfrom
feat/herdr-claude-profile
Sep 4, 2026
Merged

feat: a herdr pane says which Claude account its agent is running as#573
blooop merged 5 commits into
mainfrom
feat/herdr-claude-profile

Conversation

@JSmithRobotics

@JSmithRobotics JSmithRobotics commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Two behaviours, and the second is the one that was actually wrong.

A pane's row says which account the agent in it is running as

Reported to herdr as the display label profile=<name>. A
profile is chosen per launch and forwarded per session, so two tabs side by side
can be two different accounts with nothing on screen to tell them apart -- and the
failure that matters here is not noticing.

Reported under devlaunch's own source name, so it sits beside herdr's own labels
rather than overwriting one, and cleared rather than left stale when a launch
forwards the default login.

A pane opened beside an agent inherits that agent's profile

The pane shell shipped in 0.29.0 already opens in the workspace its tab holds. A
workspace is not an account
, so it read the default login while the agent one
pane over ran as another -- the right container under the wrong identity, which is
the same class of mistake profiles exist to prevent.

It is read from the agent's own argv rather than from a note kept anywhere, for the
reason the pane shell keeps nothing: the argv is what is true, and a record would be
a second copy of it that can go stale.

Two details in that read, both deliberate:

  • An exact element match, and it stops at a bare --. A prompt that contains
    the words --claude-profile work is a prompt.
  • Not gated on the program name. The argv is dl's own transport line either way,
    and gating on it would be a second thing to keep in step with the transport.

Nothing is inherited across a boundary

  • A pane in a tab holding no agent has no account to inherit, and gets the
    default login.
  • The environment of an agent already running is fixed at exec, so switching
    profile reaches the next session and never the one on screen. Worth stating
    because a label makes the opposite look plausible.

Public surface

One row removed and three added, and the removal is the interesting half:
PaneDestination::Workspace stops being a tuple variant and becomes a struct
variant carrying workspace_id and claude_profile.

That is a breaking change to anything constructing or matching it
positionally
, and it lives in the tripwire file rather than the promise file, so
the snapshot is the only place it shows. dl is the sole caller today, which is
exactly why it is worth writing down: nothing else would have said so.

The variant grew a field rather than gaining a sibling because the profile is a
property of the destination, not a different kind of destination.

One comment reworded for a secret scanner

flows/session_manager.rs documents herdr's argument grammar in a test comment,
and it read a bare --token NAME=VALUE. GitGuardian's Generic CLI Secret
detector reads that as a credential passed on a command line, and the "secret" it
found is the literal placeholder. It now reads `--token` as `NAME=VALUE`,
which is the style the same sentence already uses in clients/herdr.rs forty
lines away -- where it was never flagged.

An accommodation, not a correction. The old wording was accurate; nothing was
leaked and nothing needs rotating. It is folded into the commit that introduced
the line rather than sitting on top as a follow-up, and that detail is the
interesting one: the scan is per commit over the whole pull request, so a later
commit cannot clear a finding in an earlier one. Rewording in a follow-up left
the check red and pointing at the original commit.

If the detector fires again on this vocabulary -- PROFILE_TOKEN, or
args.push("--token") -- the answer is a repository-level ignore rather than more
rewording. token is herdr's word for a display label and
pane report-metadata --token NAME=VALUE is the command dl actually runs, so
code renamed to dodge a scanner would be lying about the CLI it drives.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AdSFnBdxie6TosHVmjLY28

Summary by Sourcery

Show and propagate the Claude account associated with each running agent so adjacent panes use the correct identity and reused panes never display stale profile information.

New Features:

  • Display each Claude pane's active profile in herdr metadata, clearing the label when the launch uses the default login.
  • Make panes opened beside an existing agent inherit that agent's Claude profile while preserving the default login for panes without an agent.

Bug Fixes:

  • Prevent stale account labels and mismatched identities between an agent and its adjacent pane, including launches that fail before session-manager setup completes.

Enhancements:

  • Extend the pane destination API to carry the profile associated with the live workspace session and parse profile arguments directly from the agent's argv with exact flag and -- handling.

Documentation:

  • Document the new pane account labeling and profile inheritance behavior in the changelog.

Tests:

  • Add coverage for profile metadata reporting and clearing, argv parsing, boundary handling, inheritance, and reporting failures that must not prevent session startup.

Chores:

  • Reword a test comment to avoid false positives from secret scanning without changing behavior.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @JSmithRobotics, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 7 days by commenting @sourcery-ai review. Upgrade to get a review now.

@sourcery-ai

sourcery-ai Bot commented Sep 4, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR adds profile identity visibility to herdr and carries the live agent's Claude profile into adjacent pane shells by reporting metadata at launch, parsing the profile from the agent pane's argv, and extending the workspace destination to transport the inherited profile while preserving default behavior across pane and process boundaries.

Sequence diagram for profile reporting and pane inheritance

sequenceDiagram
    participant DL as dl
    participant AgentPane as Agent pane
    participant Herdr as herdr
    participant Shell as Adjacent pane shell

    DL->>AgentPane: Launch agent with claude_profile
    DL->>Herdr: report_profile(pane_id, profile)
    Herdr-->>AgentPane: Display profile=name or clear profile
    Shell->>AgentPane: Read foreground argv
    AgentPane-->>Shell: workspace_id and claude_profile
    Shell->>DL: Attach workspace with claude_profile
    DL-->>Shell: Start next session using inherited profile
Loading

Flow diagram for exact Claude profile detection

flowchart TD
    A[Read foreground process argv] --> B{Encounter bare --?}
    B -- Yes --> C[No profile]
    B -- No --> D{Match --claude-profile or --claude-profile=name?}
    D -- Yes --> E[Return profile name]
    D -- No --> A
    C --> F[Use default login]
    E --> G[Inherit profile for adjacent pane]
Loading

File-Level Changes

Change Details Files
Expose the active Claude profile in herdr pane metadata, including stale-label cleanup when no profile is used.
  • Add shared source and token constants plus argv construction for setting or clearing profile=<name>.
  • Report profile metadata during session launch without allowing reporting failures to affect the session.
  • Add coverage for command construction, source consistency, clearing behavior, and failure tolerance.
rust/devlaunch-core/src/clients/herdr.rs
rust/devlaunch-core/src/flows/launch.rs
rust/devlaunch-core/src/flows/session_manager.rs
CHANGELOG.md
Propagate the sibling agent's launch profile when opening a pane shell.
  • Parse the profile directly from the matching pane's foreground argv in both flag forms.
  • Stop parsing at --, require exact argv elements, and avoid program-name gating.
  • Use the same pane's workspace and profile to construct the destination, defaulting to no profile when no agent is present.
  • Add tests covering parsing edge cases, pane coupling, and profile absence.
rust/devlaunch-core/src/flows/session_manager.rs
rust/dl/src/commands.rs
Make the pane destination API explicitly carry inherited profile state.
  • Replace positional PaneDestination::Workspace(String) construction and matching with a struct variant containing workspace_id and claude_profile.
  • Update the public API tripwire and affected tests to capture the breaking shape change.
rust/devlaunch-core/src/flows/session_manager.rs
rust/dl/src/commands.rs
rust/devlaunch-core/public-api.rest.txt

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@gitguardian

gitguardian Bot commented Sep 4, 2026

Copy link
Copy Markdown

️✅ There are no secrets present in this pull request anymore.

If these secrets were true positive and are still valid, we highly recommend you to revoke them.
While these secrets were previously flagged, we no longer have a reference to the
specific commits where they were detected. Once a secret has been leaked into a git
repository, you should consider it compromised, even if it was deleted immediately.
Find here more information about risks.


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@codecov

codecov Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.71429% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.72%. Comparing base (26e6369) to head (485162c).

Files with missing lines Patch % Lines
rust/dl/src/commands.rs 0.00% 9 Missing ⚠️
Additional details and impacted files
Flag Coverage Δ
python 42.98% <ø> (ø)
rust 94.99% <95.71%> (+0.12%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
shipped code (rust) 94.99% <95.71%> (+0.12%) ⬆️
harness and tooling (python) 42.98% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@JSmithRobotics
JSmithRobotics force-pushed the feat/herdr-claude-profile branch from 2c10ced to cc3f2c3 Compare September 4, 2026 10:52
@JSmithRobotics

Copy link
Copy Markdown
Collaborator Author

GitGuardian's finding on this PR was a false positive

The check flagged 1 secret in d900d74 as a Generic CLI Secret. What it
found, at flows/session_manager.rs:1203, was this comment documenting herdr's
argument grammar, and the "secret" is the literal placeholder NAME=VALUE:

// `[OPTIONS] --source <ID> <PANE_ID>`, with --token NAME=VALUE and

Nothing was leaked and there is nothing to rotate.

The giveaway is that the same sentence forty lines away in clients/herdr.rs
passes, where it happens to be written `--token NAME=VALUE`: the detector
matches a bare --token <value>, and a backtick before the flag breaks the
match. The verdict turned on markdown punctuation inside a Rust comment, in the
same commit either way.

It is green now, with the comment written the way its sibling already writes it.
Two notes for the next time this comes up.

A follow-up commit cannot clear one of these. That was my first attempt, and
the check stayed red and kept pointing at the original commit: the scan covers
every commit in the pull request, so a reword has to be folded into the commit
that introduced the line rather than added on top.

If it fires again on this vocabulary, a repository-level ignore is the right
instrument, not more rewording.
token is herdr's word for a display label,
and pane report-metadata --source <ID> --token NAME=VALUE is the command dl
actually runs, so the tree keeps

pub(crate) const PROFILE_TOKEN: &str = "profile";
args.push("--token".to_owned());

Renaming either to satisfy a scanner would leave the code lying about the CLI it
drives, and it would recur every time this area is touched. Either resolve it as
a false positive in the GitGuardian dashboard, which I have no access to, or add
a .gitguardian.yaml ignoring generic_cli_option_secret for rust/**. I would
mildly prefer the second, because it documents the decision where a contributor
will find it rather than in a dashboard they cannot see. Happy to open that as
its own PR if you want it.

For what it is worth the check was never merge-blocking: main's ruleset
requires gate and prek only. But a red tick that everyone learns to ignore is
worse than no tick, which is why it seemed worth writing down rather than waving
through.

@blooop blooop left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pane label is a good idea, and reading the account off the neighbour's own argv rather than a kept record is the right call for the reason given.

I tried hard to break the argv scraper and mostly could not: --claude-profile as the last element, --claude-profile= with an empty value, a prompt containing the flag (aid joins the prompt into one argument after --), and another flag's value being the literal string all behave. The = branch is load-bearing — aid emits --claude-profile=work via the pass-through at rewrite.rs:647 — so the body's "an exact element match" understates the code rather than the reverse.

What survives is one genuine argv disagreement, plus the label being reported before it is known to be true.

Untested: nothing drives destination_for to a non-None profile (both updated destination tests assert claude_profile: None), and nothing asserts a launch actually calls report-metadata.

§4: two doc-comment insertions landed inside the previous item's block. session_manager.rs:572 puts 26 lines — the transport table, the both_transports_… reference, the ssh_host rationale — onto profile_among's rustdoc, so its published docs claim it reads dl's two transports, and workspace_named_by (:615) is left with none. Same mistake at :1193, where both_transports_name_the_workspace_they_were_built_for's doc now documents the new fn process helper.

Spec axis: no spec available (no Closes #n, no linked issue), so that axis was skipped.

if let Some(value) = argument.strip_prefix("--claude-profile=") {
return (!value.is_empty()).then_some(value);
}
if argument == "--claude-profile" {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

profile_named_by returns on the first occurrence; clap takes the last. claude_profile: Option<String> is ArgAction::Set under clap 4 (rust/dl/src/cli.rs:623), which last-wins and does not error on a repeat, and aid forwards both pairs in order (rust/aid/src/rewrite.rs:641-645).

So dl ws --claude-profile work --claude-profile personal runs the agent as personal and hands the sibling pane work. A pane silently signed in as somebody else is the exact failure this PR closes, reached through the reader it adds. Keep scanning and keep the last hit.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correction, and this one is mine: this is not a defect and I was wrong to raise it.

I claimed clap keeps the last value for a repeated --claude-profile. It does not accept one at all:

$ dl ws --claude-profile work --claude-profile personal
error: the argument '--claude-profile <NAME>' cannot be used multiple times

Option<String> without ArgAction::Append refuses a repeat outright, so no agent is ever running with two of them in its argv and profile_named_by can never see one. First-versus-last is unreachable, and reading the first is correct as written.

Sorry for the round trip. I checked this by running the binary rather than reasoning about clap, which is what I should have done before posting.

The one thing worth keeping from it: the reader looks like it has a first-versus-last choice to make, and it would genuinely have one if that flag ever gained ArgAction::Append. I have pushed a comment saying why it does not, with a test (a_repeated_profile_never_reaches_a_running_agent) that pins the consequence, so a future change to the flag fails something and says so. No behaviour change.

Comment thread rust/devlaunch-core/src/flows/launch.rs Outdated
// rather than leaving the name of a directory as the only clue. Called
// even when no profile was named, because a pane is reused and a stale
// label is worse than none.
session_manager::report_profile(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stale-label clear fires on one exit out of five. report_profile sits inside the Prepared::Ready arm, but every other exit from begin_reporting — no ssh config (:2174), no published alias (:2186), start_forward failed (:2199), Prepared::Refused (:2219) — returns without it, while the session still opens (:2149-2150: "never fatal … the session opens regardless").

Sequence: pane P runs dl ws --claude-profile work, gets profile=work, exits. The next launch in P is dl ws2 before devpod has published ws2's alias → returns at :2186 → the sidebar still asserts profile=work over a session on the default login. That is the mislabelling the commit message says clearing exists to prevent. Calling report_profile right after herdr::Reporting::resolve succeeds (:2097) would cover all five.

Comment thread rust/devlaunch-core/src/flows/launch.rs Outdated
session_manager::report_profile(
session.runner,
&reporting,
session.host.claude.profile.as_deref(),

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This passes host.claude.profile — the raw CLI string — and begin_reporting runs at :2098, before forwarded_claude()? at :2253. So the label reports what was asked for, not what was forwarded, and three inputs make it lie:

  • dl ws --claude-profile wrok → pane labelled profile=wrok, then the launch is refused (:2031-2048). A label on a pane with no session, never cleared. Same for ../../etc.
  • DEVLAUNCH_NO_CLAUDE_TOKEN=1 dl ws --claude-profile workOptedOut, nothing forwarded, label says work.
  • A repo whose devcontainer mounts its own Claude config → claude_seen != OursOk(None) (:2022), nothing forwarded, label says work.

Also --claude-profile default, a documented spelling meaning the default login, labels profile=default rather than clearing. Reporting from the resolved outcome instead of the request would make the label mean what the sidebar implies it means.

@blooop
blooop force-pushed the feat/herdr-claude-profile branch from cc3f2c3 to bcf6665 Compare September 4, 2026 11:46
@blooop
blooop force-pushed the feat/herdr-claude-profile branch from bcf6665 to 629be1d Compare September 4, 2026 11:48
@blooop
blooop force-pushed the feat/herdr-claude-profile branch from 629be1d to 30baeb8 Compare September 4, 2026 11:52
Base automatically changed from feat/claude-profiles-list to main September 4, 2026 11:56
JSmithRobotics and others added 5 commits September 4, 2026 12:56
…with

`--herdr-shell` re-enters as `dl <ws>`, and it was passing `claude_profile:
None`. So a session started with `--claude-profile work` got a sibling pane
authenticated as the *default* account, which is the one way this feature could
mislead quietly: the pane looks like the agent's shell and is signed in as
somebody else.

Read from the live sibling rather than remembered, which is what lets it exist
at all. A profile is named per launch and deliberately not stored with the
workspace, so there is nothing on disk to inherit; the running process is the
only record. `pane_destination` is already asking herdr what the tab's panes are
running, so the profile costs no round trip -- `profile_among` reads the same
`PaneProcessInfo` that `workspace_among` does, and reads it off the *same* pane,
so a tab holding two sessions cannot pair one session's workspace with another's
account.

Two properties of the reader are load-bearing, and one case decides both.
`aid <ws> add --claude-profile support to the docs` becomes a dl line whose
prompt is a single argument holding that text, and the ssh transport carries the
same string as one payload argument. Elements are compared whole, so neither is
an argv element equal to `--claude-profile` and neither is read as a flag; and
the scan stops at the first bare `--`, which says it a second way and cheaply. A
real flag before the `--` is still read, so the stop is a stop and not a refusal.

Deliberately not gated on the program name. `dl` on a host may be `dl`,
`dl-next` or an absolute path, and matching that family by prefix is exactly the
fuzzy test that made `ssh_host` necessary -- a sibling pane running
`ssh -F myconf.devpod somehost` once made the pane shell claim a workspace
called `myconf`. The pane-level coupling is the stronger guard: the profile is
asked only of a pane that has already named a workspace, so an unrelated pane is
never consulted.

`PaneDestination::Workspace` becomes a struct variant and moves the public-API
snapshot.
`dl --claude-profiles` names the account behind each profile, but only where
somebody thinks to look. herdr's sidebar is where the panes actually are, and it
had the directory name or nothing.

So a launch that resolved a `Reporting` also reports the profile as a
display-only metadata token, which herdr renders as `$profile` in an agent row.
A token rather than `--display-agent`: that field is the agent name
`report-agent` sets and the one herdr picks a detection manifest by, so writing
a profile into it would corrupt state detection to gain a label.

**Clearing matters as much as setting**, and is why this is called with `None`
too. A pane is reused. A launch naming `work` followed by one naming nothing
would otherwise leave the sidebar asserting an account the running session is
not using -- the exact mislabelling `--claude-profiles` exists to prevent, moved
somewhere more visible and more trusted.

Display only, so it cannot disturb the idle/working/blocked the Claude Code hook
reports through the forwarded socket. And reported then tolerated, like
everything else in this flow: a herdr that is gone, too old for
`report-metadata` or merely slow costs the label and never the session. The
signature carries most of that guarantee, since there is no outcome to branch
on.

Syntax verified against herdr 0.8.2 rather than guessed: the socket-API docs
name the method `pane.report_metadata` and document no CLI wrapper, so herdr was
installed from conda-forge and asked. `pane report-metadata [OPTIONS] --source
<ID> <PANE_ID>`, with `--token NAME=VALUE` and `--clear-token NAME`. The pane id
is given first, matching the `report-agent` invocation in HOOK that was measured
against a live herdr. A guessed argv would have been a feature that never worked
and never said so, since every call in this subsystem ends `>/dev/null 2>&1`.

`devlaunch:claude` was a bare literal in HOOK and is now a named constant with
the diff test the standing rule asks for.
One row removed and three added, and the removal is the interesting half:
`PaneDestination::Workspace` stops being a tuple variant and becomes a struct
variant carrying `workspace_id` and `claude_profile`.

That is a breaking change to anything that constructs or matches it positionally,
and it is in the tripwire file rather than the promise file, so the snapshot is
the only place it shows. It is in-tree only today -- `dl` is the sole caller --
which is exactly why it is worth writing down: nothing else would have said so.

The variant grew a field rather than gaining a sibling because the profile is a
property of the destination, not a different kind of destination: a pane opened
beside an agent inherits the account that agent is running as, and a pane opened
anywhere else has no account to inherit.

Regenerated with `scripts/public-api-snapshots.sh` on nightly 1.100.0
(2026-09-03) and cargo-public-api 0.52.0, the pin the script names.
Two behaviours, and the second is the one worth the words: a pane opened beside
an agent inherits that agent's account, where before it opened in the right
workspace under the wrong login. A workspace is not an account.
…can end

**`report_profile` sat in `begin_reporting`'s `Ready` arm, which is one exit out
of five.** No ssh config, no published alias, a forward that would not start and a
container that refused all return `None` from there, and every one of them still
opens the session: those notices are never fatal by design. So a pane that had run
`dl ws --claude-profile work` and was then reused for a `dl ws2` whose alias devpod
had not published yet kept asserting `profile=work` over a session on the default
login. That is exactly the stale label the clearing was added to prevent, on the
four paths where clearing never ran.

It is reported first now, before anything that can fail. Safe there because the
label is host-side: `report_profile` runs herdr's own binary against a pane id and
needs neither the forward nor a prepared container.
`the_profile_label_is_reported_even_when_the_manager_cannot_be_reached` drives the
no-ssh-config arm and fails without the move.

**Two doc comments were inserted inside the previous item's block.** In
`session_manager.rs`, `profile_among`'s doc was appended to `workspace_named_by`'s,
so 26 lines about dl's two transports (the table, the `both_transports_...`
reference, the `ssh_host` rationale) became `profile_among`'s published rustdoc and
`workspace_named_by` was left with none. The same slip in the test module gave
`both_transports_name_the_workspace_they_were_built_for`'s doc to the new `process`
helper. Both blocks are back above what they describe; no code moved.

**And a note where a reader would look for one.** `profile_named_by` returns the
first match and looks like it is choosing between first and last. It is not:
`--claude-profile` is an `Option<String>` with no `ArgAction::Append`, so clap
refuses a repeat outright and no agent ever runs with two of them in its argv.
`a_repeated_profile_never_reaches_a_running_agent` pins the consequence, so a
future `ArgAction::Append` on that flag fails something and says so.
@blooop
blooop force-pushed the feat/herdr-claude-profile branch from 30baeb8 to 485162c Compare September 4, 2026 11:56
@blooop
blooop merged commit 79457e9 into main Sep 4, 2026
15 checks passed
@blooop
blooop deleted the feat/herdr-claude-profile branch September 4, 2026 12:01
@blooop blooop mentioned this pull request Sep 4, 2026
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