Group Management
Group Management
Groups are Logship's lightweight routing and ownership primitive.
They are intentionally simple: a group has a name, an optional description, and a set of member users. The reason they matter is what they unlock elsewhere in the system. In particular, incident ownership and incident notifications both rely on groups to route responsibility to a team instead of a single person.
If you only need the short version, here it is: create a group, add the right people, use that group as the owner for incidents, and Logship will fan notifications out to the members of that group automatically.
What a group contains
Each group stores the fields below:
| Field | Meaning |
|---|---|
groupId | Stable group identifier |
name | Required display name |
description | Optional description or purpose |
createdByUserId | User who originally created the group |
createdAt | Creation timestamp |
updatedAt | Last update timestamp |
Membership is stored separately from the group itself.
Each group membership record stores:
| Field | Meaning |
|---|---|
groupId | The group the membership belongs to |
userId | The user in the group |
addedByUserId | Who added the member |
addedAt | When the member was added |
What groups are for
Groups are primarily used for team-based operations:
- Incident ownership
- Incident notification fan-out
- Operational routing
- Keeping accountability at the team level while assigning execution to a person
In practice, the most common pattern is:
- Group = accountable team
- Incident assignee = current responder
That gives you both durable ownership and a clear execution point.
Core behavior
The group system supports four main operations:
| Operation | What it does |
|---|---|
| Create/update a group | Save the group's metadata |
| Delete a group | Remove the group and its memberships, if it is safe to do so |
| Add a member | Add a user to the group |
| Remove a member | Remove a user from the group |
There is no separate lifecycle state model for groups. Groups are either present or deleted.
Creating and editing groups
Required fields
The only required field is name.
If the name is empty or whitespace, the save is rejected.
Optional fields
description is optional. Empty descriptions are stored as absent rather than as required content.
Timestamps and authorship
When a group is saved:
createdByUserIdis preserved if the caller provides one- otherwise the current user becomes the creator
createdAtis preserved if supplied- otherwise it defaults to the current time
updatedAtis always set to "now"
Upsert behavior
Group saves use an upsert model under the hood.
That means the backend uses INSERT OR REPLACE semantics rather than distinct "create" and "update" endpoints. The frontend still treats create and edit as different UX flows, but the API surface is intentionally compact.
Membership management
Membership is also intentionally simple.
Adding a member
To add a member:
- the target group must exist
- the target user must be a member of the same account
If either validation fails, the request is rejected.
Removing a member
Removing a member deletes that specific groupId + userId membership row.
There is no extra state transition, approval flow, or soft-delete behavior.
Idempotent add behavior
Adding a member uses INSERT OR REPLACE semantics in the data store.
Practically, that means:
- adding the same user again does not create duplicate rows
- the latest add operation becomes the stored membership record
Permissions
One thing worth calling out: there is not a dedicated Group.* permission family today.
Group management is tied to the incident permission model.
Read access
Users can read group data if they have any of the following:
Logship.Account.AdminLogship.Incident.ViewerLogship.Incident.EditorLogship.Incident.Manager- global admin access
Management access
Creating, editing, deleting groups, and changing membership is more restricted.
Those write operations require one of:
Logship.Account.AdminLogship.Incident.Manager- global admin access
Notably, Logship.Incident.Editor can work with incidents, but it does not have enough permission to manage groups.
Delete behavior and safety rules
Deleting a group is not just a blind remove.
Before the backend deletes a group, it checks whether that group still owns any incidents.
Delete rule
If the group still owns one or more incidents, deletion is blocked.
The backend returns an error in the shape of:
Incident group {groupId} still owns {incidentCount} incidents. What happens on successful delete
If the group is safe to delete:
- All memberships for the group are deleted.
- The group record itself is deleted.
There is no special archival step and no automatic incident reassignment because the delete is blocked until ownership is cleared.
Relationship to incidents
This is the most important reason to document groups well: they are not isolated. They directly affect incident behavior.
Groups as incident owners
An incident can be owned by either:
- a user
- a group
When a group owns an incident, the group acts as the accountable team.
Groups and notifications
When the incident notification service resolves recipients for a group-owned incident, it notifies all members of the owner group.
That means group membership changes have operational impact immediately:
- add someone to the group and they become part of future incident notifications
- remove someone from the group and they stop receiving future group-owner incident notifications
Groups and assignment
A common and well-supported pattern is:
- incident owner = group
- incident assignee = a single user
This lets the team remain accountable while still making it obvious who is actively driving the response.
What groups do not currently do
It is also useful to be explicit about what the group system does not provide today:
- No group-specific state machine
- No dedicated group history timeline
- No built-in group notifications when membership changes
- No approval workflow for adds/removals
- No role levels inside a group
- No nested groups
That simplicity is by design. Groups are operational routing objects, not a full organizational hierarchy system.
Current UI behavior
The current UI exposes two main group screens:
Group list
The groups list page is optimized for dense operational scanning. It includes:
- Total group count
- Count of groups with members
- Count of empty groups
- Search across names, descriptions, creators, IDs, and member counts
- Filtering by creator
- Filtering by membership status
- Row-level navigation into group details
Group edit page
The group edit page supports:
- Creating a new group
- Editing name and description
- Viewing creator and timestamps
- Adding members from available account users
- Removing existing members
- Deleting the group when allowed
The UI only enables management actions for users with the appropriate management permissions.
API surface
The backend exposes a compact REST surface for groups.
Groups
GET /accounts/{accountId}/groups
PUT /accounts/{accountId}/groups/{groupId}
DELETE /accounts/{accountId}/groups/{groupId} Membership
GET /accounts/{accountId}/groups/{groupId}/members
PUT /accounts/{accountId}/groups/{groupId}/members/{memberUserId}
DELETE /accounts/{accountId}/groups/{groupId}/members/{memberUserId} Example payloads
Group
{
"accountId": "5e7bc3d8-6452-4cbf-860d-657a2c0e8b22",
"groupId": "1bd5df10-7b83-48a7-9060-f1f3f422464c",
"name": "Platform On-Call",
"description": "Primary responders for platform and reliability incidents.",
"createdByUserId": "d2fdf216-32a7-42c1-97f4-8b4f8daa95f7",
"createdAt": "2026-03-21T10:00:00Z",
"updatedAt": "2026-03-21T10:15:00Z"
} Group member
{
"accountId": "5e7bc3d8-6452-4cbf-860d-657a2c0e8b22",
"groupId": "1bd5df10-7b83-48a7-9060-f1f3f422464c",
"userId": "fe6f1d13-ff6d-4c99-8cb3-514b9390e40d",
"addedByUserId": "d2fdf216-32a7-42c1-97f4-8b4f8daa95f7",
"addedAt": "2026-03-21T10:16:00Z"
} Recommended operating patterns
Pattern: team ownership for incidents
Create groups that match the teams who should own incidents operationally, not necessarily the org chart exactly.
Good examples:
Platform On-CallDatabase ReliabilitySecurity ResponseCustomer Escalations
These names make ownership obvious in the incident UI and in notifications.
Pattern: keep membership current
Because group membership directly affects incident notifications, stale membership turns into stale paging and stale accountability very quickly.
Treat group membership as operational configuration, not as passive documentation.
Pattern: avoid empty "placeholder" groups
The UI makes empty groups visible for a reason. An empty group can technically exist, but it is usually a sign that ownership routing is incomplete.
Pattern: clear incident ownership before deleting
If you want to retire a group:
- Reassign any incidents it still owns
- Confirm it no longer owns active work
- Then delete the group
This matches the backend delete guard and avoids operational surprises.
Troubleshooting and gotchas
"Group name is required."
The save request did not include a usable name.
"No permission."
The caller is authenticated but does not have enough permission for the requested group operation.
"Incident group ... does not exist."
You tried to add a member to a group that is missing or invalid.
"User ... is not a member of this account."
Only users who belong to the account can be added to a group.
"Incident group ... still owns ... incidents."
You tried to delete a group that is still the owner of one or more incidents. Reassign those incidents first.
A user was added twice
This does not create duplicate membership rows. Membership adds are effectively idempotent because the store uses replace semantics.
Why didn't a group change create a notification?
Group CRUD and membership changes do not currently publish notifications on their own. Their impact shows up indirectly through incident ownership and future incident notifications.
Implementation references
If you need to trace behavior in code, start here:
src\Logship\Service\Services\IncidentManager\GroupManagementWebController.cssrc\Logship\Host\ConsoleHost\Apis\Backend\Groups.cssrc\Logship\App\fe-react\ClientApp\src\services\groups\groupservice.tssrc\Logship\App\fe-react\ClientApp\src\routes\groups\index\groups-index.tsxsrc\Logship\App\fe-react\ClientApp\src\routes\groups\edit\groups-edit.tsxsrc\Logship\Service\Services\Database\DatabaseConnection\Sqlite\SqliteDatabaseConnection.cssrc\Logship\Service\Services\Accounts\Permissioning\AuthPermissions.cs
If you are changing how groups affect incidents, also review:
src\Logship\Service\Services\IncidentManager\IncidentManagerWebController.cssrc\Logship\Service\Services\IncidentManager\IncidentAssignmentNotificationService.cs
That pairing is what turns groups from simple records into operational routing and notification targets.