HumanSignal/label-studio · error · ValueError

Task is required to resolve URI={uri}

Error message

Task is required to resolve URI={uri}

What it means

When resolving storage URIs into servable URLs, the storage needs a Task to build the per-task proxy URL (via the task-storage-data-resolve route). resolve_uri raises this ValueError when can_resolve_url matched the URI but the task argument is None, because the proxy link cannot be constructed without a task id.

Source

Thrown at label_studio/io_storages/base_models.py:419

        elif isinstance(uri, dict):
            resolved = {}
            for key in uri.keys():
                result = self.resolve_uri(uri[key], task)
                resolved[key] = result if result else uri[key]
            return resolved

        # string: process one url
        elif isinstance(uri, str) and self.url_scheme in uri:
            try:
                # extract uri first from task data
                extracted_uri, _ = get_uri_via_regex(uri, prefixes=(self.url_scheme,))
                if not self.can_resolve_url(extracted_uri):
                    logger.debug(f'No storage info found for URI={uri}')
                    return

                if task is None:
                    logger.error(f'Task is required to resolve URI={uri}', exc_info=True)
                    raise ValueError(f'Task is required to resolve URI={uri}')

                proxy_url = urljoin(
                    settings.HOSTNAME,
                    reverse('storages:task-storage-data-resolve', kwargs={'task_id': task.id})
                    + f'?fileuri={base64.urlsafe_b64encode(extracted_uri.encode()).decode()}',
                )
                return uri.replace(extracted_uri, proxy_url)
            except Exception:
                logger.info(f"Can't resolve URI={uri}", exc_info=True)

    def resolve_uris(self, data, task):
        """Resolve all cloud storage URIs in data, replacing each with a proxy URL.

        Unlike resolve_uri which only handles the first URI in a string,
        this finds and replaces every URI matching this storage's scheme.
        Handles str, list, and dict data recursively.

        Returns the resolved data, or None if nothing was resolved.

View on GitHub (pinned to 0b49e9b539)

Solutions

  1. Pass the Task instance that owns the data (task.id is used to build the proxy URL).
  2. Only call resolve_uri when you actually have a task context; otherwise skip resolution for that URI.
  3. If building tooling, fetch the task first (Task.objects.get) or resolve the file directly via the storage's HTTP URL generator instead.

Example fix

// before
uri = storage.resolve_uri('s3://bucket/file.jpg', task=None)

// after
task = Task.objects.get(id=task_id)
uri = storage.resolve_uri('s3://bucket/file.jpg', task=task)
Defensive patterns

Strategy: validation

Validate before calling

if task is None:
    raise ValueError('resolve_uri requires a Task instance to build the proxy URL')
uri_to_resolve = uri
if not storage.can_resolve_url(uri_to_resolve):
    return  # nothing to resolve

Type guard

def has_task(task) -> bool:
    return task is not None and getattr(task, 'id', None) is not None

Try / catch

try:
    url = storage.resolve_uri(uri, task=task)
except ValueError as e:
    if 'Task is required' in str(e):
        url = None  # resolution requires task context; skip or handle manually
    else:
        raise

Prevention

When it happens

Trigger: Calling resolve_uri(uri, task=None) (or a caller propagating a missing task) for a URI whose scheme matches the storage's url_scheme and passes can_resolve_url.

Common situations: Resolving URIs outside of a task context (e.g. in import previews or export tooling) where no Task instance exists; passing task=None by default in wrapper utilities.

Related errors


AI-assisted analysis of HumanSignal/label-studio@0b49e9b539 (2026-08-29). Data as JSON: /api/errors/f022ad6a947e0b46. Report an issue: GitHub.