block/buzz · error
member p tag
Error message
member p tag
What it means
Identical family to the owner p tag: the test parses the member's role p tag with Tag::parse(["p", hex::encode(member), "", role]). The expect panics when the nostr crate rejects the p tag because the member bytes are not a valid 32-byte pubkey or the tag shape fails that crate version's validation.
Source
Thrown at crates/buzz-db/src/store/channel_members.rs:3175
"INSERT INTO channel_members (community_id, channel_id, pubkey, role, invited_by) \
VALUES ($1, $2, $3, 'admin', $4)",
)
.bind(community_uuid)
.bind(channel)
.bind(member.as_slice())
.bind(owner.as_slice())
.execute(&pool)
.await
.expect("seed canonical admin");
let roster = |role: &str, timestamp| {
EventBuilder::new(Kind::Custom(39002), "")
.tags(vec![
Tag::parse(["d", channel.to_string().as_str()]).expect("d tag"),
Tag::parse(["p", hex::encode(owner).as_str(), "", "owner"])
.expect("owner p tag"),
Tag::parse(["p", hex::encode(member).as_str(), "", role])
.expect("member p tag"),
])
.custom_created_at(Timestamp::from(timestamp))
.sign_with_keys(&relay_keys)
.expect("sign roster")
};
let base = Timestamp::now().as_secs();
let fresh = roster("admin", base);
assert!(
db.replace_addressable_event(community, &fresh, Some(channel))
.await
.expect("publish canonical role")
.1
);
let stale = roster("member", base + 1);
let error = db
.replace_addressable_event(community, &stale, Some(channel))
.await
.expect_err("desired-state fence must reject stale role");View on GitHub (pinned to dad5a33865)
Solutions
- Assert member.len() == 32 and role is non-empty before Tag::parse
- Log hex::encode(member) and role in the panic message to diagnose
- Upgrade or pin the nostr crate to a version whose Tag::parse accepts the tag shape
- Use typed constructors (Tag::public_key / Tag::custom) instead of generic parse
Example fix
// before
Tag::parse(["p", hex::encode(member).as_str(), "", role]).expect("member p tag"),
// after
assert_eq!(member.len(), 32, "member pubkey must be 32 bytes");
assert!(!role.is_empty(), "role must be non-empty");
Tag::parse(["p", hex::encode(member).as_str(), "", role]).expect("member p tag"), Defensive patterns
Strategy: validation
Validate before calling
assert_eq!(member.len(), 32); assert!(!role.is_empty(), "role string must be non-empty");
Type guard
fn valid_member_p_tag(pubkey: &[u8], role: &str) -> bool {
pubkey.len() == 32 && !role.is_empty() && Tag::parse(["p", &hex::encode(pubkey), "", role]).is_ok()
} Try / catch
let tag = Tag::parse(["p", hex::encode(member).as_str(), "", role])
.unwrap_or_else(|e| panic!("member p tag invalid (member={}, role={role}): {e}", hex::encode(member))); Prevention
- Same as owner p tag: 32-byte keys, non-empty role values
- Add a one-time validation loop over all closure role inputs in the test
- Keep nostr crate pinned and review its Tag::parse changes on upgrade
When it happens
Trigger: Tag::parse errors for ["p", member_hex, "", role] when member is not 32 bytes, is all zeros/empty, or `role` is empty at call time (the closure receives role strings like "owner"/"member"; an empty string may be rejected by stricter versions).
Common situations: member buffer built from wrong-length data; crate upgrade tightened validation of p-tag third/fourth elements; calling the closure with an empty role string in a test case.
Related errors
- d tag
- owner p tag
- moderation notice recipient must be a 32-byte pubkey, got {}
- failed to sign moderation notice: {e}
- failed to sign system message: {e}
AI-assisted analysis of block/buzz@dad5a33865 (2026-08-30).
Data as JSON: /api/errors/c54bb5ee853f564c.
Report an issue: GitHub.