RocketChat/Rocket.Chat · error · Meteor.Error

error-cannot-delete-team-channel

error-cannot-delete-team-channel

Error message

Cannot delete a team channel

What it means

Thrown by DELETE/POST rooms.eraseRoom when the target room has its 'teamMain' flag set. Rocket.Chat teams own one primary channel that anchors the team; deleting it would orphan the team, so the server refuses. To remove it you must delete the whole team, not the channel in isolation.

Source

Thrown at apps/meteor/server/api/v1/rooms.ts:208

				additionalProperties: false,
			}),
			400: validateBadRequestErrorResponse,
			401: validateUnauthorizedErrorResponse,
		},
	},
	async function action() {
		const { roomId } = this.bodyParams;

		const room = await Rooms.findOneById(roomId);

		if (!room) {
			throw new MeteorError('error-invalid-room', 'Invalid room', {
				method: 'eraseRoom',
			});
		}

		if (room.teamMain) {
			throw new Meteor.Error('error-cannot-delete-team-channel', 'Cannot delete a team channel', {
				method: 'eraseRoom',
			});
		}

		await eraseRoom(room, this.user);

		return API.v1.success();
	},
);

API.v1.get(
	'rooms.get',
	{
		authRequired: true,
		response: {
			200: ajv.compile<{ update: IRoom[]; remove: IRoom[] }>({
				type: 'object',
				properties: {

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. If you want the team gone, delete the team (teams.delete / team removal flow) instead of the single channel.
  2. If you must remove the channel, first convert the team so this room is no longer teamMain (remove the team association), then call eraseRoom.
  3. Pre-check room.teamMain via rooms.info or rooms.get before calling eraseRoom and skip or branch accordingly.
  4. Filter team-main rooms out of any bulk-delete job.

Example fix

// before
await rest.post('/api/v1/rooms.eraseRoom', { roomId });

// after
const info = await rest.get(`/api/v1/rooms.info?roomId=${roomId}`);
if (info.room.teamMain) {
  // delete the owning team instead, or unteam the channel first
  await rest.post('/api/v1/teams.delete', { teamName: info.room.teamId });
} else {
  await rest.post('/api/v1/rooms.eraseRoom', { roomId });
}
Defensive patterns

Strategy: validation

Validate before calling

const info = await rest.get(`/api/v1/rooms.info?roomId=${roomId}`);
if (info.room?.teamMain) {
  // do NOT call eraseRoom; delete the team instead
  throw new Error(`Room ${roomId} is a team's main channel; delete via teams.delete`);
}

Type guard

function isTeamMainRoom(r: { teamMain?: boolean } | undefined | null): boolean {
  return !!r?.teamMain;
}

Try / catch

try {
  await rest.post('/api/v1/rooms.eraseRoom', { roomId });
} catch (e) {
  if (isMeteorError(e, 'error-cannot-delete-team-channel')) {
    // fall back to deleting the owning team
  } else throw e;
}

Prevention

When it happens

Trigger: POST /api/v1/rooms.eraseRoom with { roomId } where that room is the main channel of a team (room.teamMain === true). Also reached by any internal caller invoking the eraseRoom action handler after the room is loaded and the teamMain check runs.

Common situations: Migration/cleanup scripts that naively iterate all rooms and delete them; converting a team back into ordinary channels; admin tooling that doesn't account for teams; CI that creates a team then tries to tear it down room-by-room.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12). Data as JSON: /api/errors/ec1055a07f278eb9. Report an issue: GitHub.