From cf3221d92a0eaaca2420f75f45a864f8cfe5fb4f Mon Sep 17 00:00:00 2001 From: sumit-m <33051892+sumit-m@users.noreply.github.com> Date: Sat, 22 Aug 2026 01:44:56 +0530 Subject: [PATCH] fix: keep the assignee on a self-assignment nostr removes a p tag naming the event author unless allow_self_tagging is set, so assigning or unassigning yourself published an event with no assignee: the comment read "Assigned this issue to ..." while the list stayed empty. Messages and forum comments already opted in; the assignment and recipient-note builders and the generic signer did not. Signed-off-by: sumit-m <33051892+sumit-m@users.noreply.github.com> --- crates/buzz-sdk/src/builders.rs | 59 ++++++++++++++++++- desktop/src-tauri/src/commands/identity.rs | 9 ++- .../commands/project_git_recipient_notes.rs | 8 ++- 3 files changed, 73 insertions(+), 3 deletions(-) diff --git a/crates/buzz-sdk/src/builders.rs b/crates/buzz-sdk/src/builders.rs index f43887b65b1..e2564bac8df 100644 --- a/crates/buzz-sdk/src/builders.rs +++ b/crates/buzz-sdk/src/builders.rs @@ -1261,7 +1261,15 @@ fn build_git_issue_assignee_operation( tags.push(tag(&["prior", &prior])?); } - Ok(EventBuilder::new(Kind::Custom(1), content).tags(tags)) + // `allow_self_tagging` because an assignee may be the signer, and that is + // the normal case: an agent claiming work, or a person clicking "Assign to + // me". `nostr` removes a `p` tag matching the author by default, which + // silently produced assignment events naming nobody — the operation + // published, the comment read "Assigned this issue to …", and the assignee + // list stayed empty. Here the `p` tags *are* the payload, not a mention. + Ok(EventBuilder::new(Kind::Custom(1), content) + .tags(tags) + .allow_self_tagging()) } /// Status to apply to a patch or issue root (kind:1630/1631/1632/1633, NIP-34). @@ -3711,6 +3719,55 @@ mod tests { assert!(matches!(err, SdkError::InvalidInput(_))); } + /// The failure this guards: `nostr` removes a `p` tag naming the author + /// unless `allow_self_tagging` is set, so a self-assignment published an + /// event with no assignee in it. The comment log still read "Assigned this + /// issue to …" while the assignee list stayed empty — for agents claiming + /// their own work and for anyone clicking "Assign to me". + #[test] + fn self_assignment_keeps_the_assignee() { + let keys = Keys::generate(); + let me = keys.public_key().to_hex(); + let repo = GitRepoCoord { + owner: "a".repeat(64), + id: "repo".to_string(), + }; + let builder = build_git_issue_assignment( + &repo, + &"d".repeat(64), + std::slice::from_ref(&me), + "Assigned this issue to me", + ) + .unwrap(); + let ev = builder.sign_with_keys(&keys).unwrap(); + assert!( + has_tag(&ev, "p", &me), + "the assignee is the payload, not a mention: {:?}", + ev.tags + ); + } + + /// Unassigning yourself is the same shape and failed the same way. + #[test] + fn self_unassignment_keeps_the_assignee() { + let keys = Keys::generate(); + let me = keys.public_key().to_hex(); + let repo = GitRepoCoord { + owner: "a".repeat(64), + id: "repo".to_string(), + }; + let ev = build_git_issue_unassignment( + &repo, + &"d".repeat(64), + std::slice::from_ref(&me), + "Unassigned me from this issue", + ) + .unwrap() + .sign_with_keys(&keys) + .unwrap(); + assert!(has_tag(&ev, "p", &me)); + } + #[test] fn git_issue_assignment_happy_path() { let owner = "a".repeat(64); diff --git a/desktop/src-tauri/src/commands/identity.rs b/desktop/src-tauri/src/commands/identity.rs index 8852fcb7e01..2e5dd57e85b 100644 --- a/desktop/src-tauri/src/commands/identity.rs +++ b/desktop/src-tauri/src/commands/identity.rs @@ -147,7 +147,14 @@ pub async fn sign_event( .map(|tag| Tag::parse(tag).map_err(|error| format!("invalid tag: {error}"))) .collect::, _>>()?; - let mut builder = EventBuilder::new(Kind::Custom(kind), content).tags(nostr_tags); + // Sign exactly the tags the caller composed. `nostr` otherwise removes + // any `p` tag naming the author, which made this signer silently drop + // part of what it was handed — the self-assignment path in + // `issueAssignments.ts` published assignments with no assignee. A + // caller that does not want a self `p` tag simply does not add one. + let mut builder = EventBuilder::new(Kind::Custom(kind), content) + .tags(nostr_tags) + .allow_self_tagging(); if let Some(created_at) = created_at { builder = builder.custom_created_at(Timestamp::from(created_at)); } diff --git a/desktop/src-tauri/src/commands/project_git_recipient_notes.rs b/desktop/src-tauri/src/commands/project_git_recipient_notes.rs index 4695749fed7..0be417d038a 100644 --- a/desktop/src-tauri/src/commands/project_git_recipient_notes.rs +++ b/desktop/src-tauri/src/commands/project_git_recipient_notes.rs @@ -116,7 +116,13 @@ fn build_labeled_recipient_note_event( .map(Tag::parse) .collect::, _>>() .map_err(|error| format!("build {label} tags: {error}"))?; - let mut builder = EventBuilder::new(Kind::TextNote, content).tags(tags); + // The recipients are the payload here, and one of them may be the signer — + // assigning yourself, or requesting your own review. `nostr` strips a `p` + // tag matching the author unless told not to, which turned those into + // events naming nobody. See the same call in `buzz-sdk`. + let mut builder = EventBuilder::new(Kind::TextNote, content) + .tags(tags) + .allow_self_tagging(); if let Some(created_at) = created_at { builder = builder.custom_created_at(Timestamp::from_secs(created_at)); }