passbolt/passbolt_api · error · BadRequestException

You do not have the permission to change a personal tag…

Error message

You do not have the permission to change a personal tag into shared tag.

What it means

This BadRequestException is thrown by UpdatePersonalTagService::update when a user attempts to update a personal (V4) tag but supplies a slug starting with '#', the prefix that marks a tag as shared. Personal tags can only be converted to shared tags via the V5 metadata flow, so the V4 update endpoint rejects the transition outright rather than silently promoting the tag.

Solutions

  1. Remove the leading '#' from the slug, or omit the slug entirely, when updating a personal tag via the V4 endpoint.
  2. If the goal is to make the tag shared, migrate to the V5 metadata tag workflow (MetadataTag upgrade services) instead of editing the slug.
  3. Check the tag's is_shared/user_id ownership before sending an update so clients can render the correct UI (no shared toggle for personal tags in V4).

Example fix

// before
await updateTag(tagId, { slug: '#team-credentials' });
// after
await updateTag(tagId, { slug: 'team-credentials' }); // keep personal, or use V5 metadata API to share
Defensive patterns

Strategy: validation

Validate before calling

if (typeof slug === 'string' && slug.startsWith('#')) throw new Error('Cannot set a shared-prefixed slug on a personal tag');

Type guard

const isPersonalSlug = (slug: unknown): slug is string => typeof slug === 'string' && !slug.startsWith('#');

Prevention

When it happens

Trigger: PATCH/PUT to the tag update endpoint with a personal tag whose DTO slug is non-null and begins with '#' (mb_substr($slug, 0, 1) === '#'), while V4 tag creation is still enabled.

Common situations: A client or script batch-renames tags and accidentally adds the '#' shared prefix; a user tries to share a personal tag using the legacy V4 API after migrating partially to V5 metadata; automation copies shared-tag slugs onto personal tags.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17). Data as JSON: /api/errors/242f613982324a17. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltEe/Tags/src/Service/Tags/UpdatePersonalTagService.php:93

                $options
            );

            if (!$tagsTable->save($entity)) {
                throw new CustomValidationException(
                    __('Unable to save the tag.'),
                    $entity->getErrors(),
                    $tagsTable
                );
            }

            return $entity;
        } else {
            $this->assertV4TagCreationEnabled();

            $slug = $dtoArray['slug'];

            if (!is_null($slug) && mb_substr($slug, 0, 1) === '#') {
                throw new BadRequestException(
                    __('You do not have the permission to change a personal tag into shared tag.')
                );
            }

            $this->assertV4DowngradeAllowed($tag);

            /** @var \Passbolt\Tags\Model\Table\ResourcesTagsTable $resourcesTagsTable */
            $resourcesTagsTable = $this->fetchTable('Passbolt/Tags.ResourcesTags');

            return $tagsTable->getConnection()->transactional(function () use (
                $tagsTable,
                $resourcesTagsTable,
                $tag,
                $slug,
                $uac
            ) {
                $newTag = $tagsTable->findOrCreateTag($slug, $uac);

View on GitHub (pinned to 31c1bbc10f)