conductor-oss/conductor · error · AccessForbiddenException

File has no workflowId

Error message

File has no workflowId: {}

What it means

Thrown by getFamilyAccessibleFile when the file record's workflowId is null or blank. Family-based read access cannot be resolved without an owning workflow in the record, so the request is denied. Raised as AccessForbiddenException (HTTP 403). This indicates corrupted/incomplete file metadata rather than a caller mistake.

Solutions

  1. Inspect the file metadata record directly to confirm its workflowId field.
  2. Repair the record by setting the correct workflowId, or recreate the file under a valid workflow.
  3. If systemic, audit the createFile path to ensure workflowId is always persisted (note: createFile already rejects blank workflowId, so a null here implies data corruption/migration).

Example fix

// before - record has null workflowId; any download attempt throws
fileStorageService.getDownloadUrl(wfId, fileId); // 403

// after - repair the metadata or recreate the file
fileMetadataDAO.updateWorkflowId(fileId, ownerWorkflowId);
fileStorageService.getDownloadUrl(ownerWorkflowId, fileId);
Defensive patterns

Strategy: try-catch

Try / catch

try {
    fileStorageService.getDownloadUrl(wfId, fileId);
} catch (AccessForbiddenException e) {
    // file record has no workflowId (data integrity issue); repair or recreate
}

Prevention

When it happens

Trigger: Calling getDownloadUrl, downloadContent, or getFileMetadata for a file whose stored workflowId is empty (data integrity problem). The resolver cannot compute a workflow family from a missing owner.

Common situations: A legacy/migrated file record without workflowId; a bug created a file metadata row with a null workflowId; the DAO returned a partially-populated model.

Related errors


AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14). Data as JSON: /api/errors/ed333fb371a36058. Report an issue: GitHub.

Appendix: source

Thrown at core/src/main/java/org/conductoross/conductor/core/storage/FileStorageServiceImpl.java:241

        fileStorage.abortMultipartUpload(model.getStoragePath(), uploadId);
    }

    /** Upload state may only be mutated by the workflow that created the file record. */
    private @NonNull FileModel getOwnedFile(String workflowId, String fileId) {
        FileModel model = getFileModelOrThrow(fileId);
        if (workflowId == null
                || workflowId.isBlank()
                || !workflowId.equals(model.getWorkflowId())) {
            throw new AccessForbiddenException("Workflow does not own file: " + fileId);
        }
        return model;
    }

    /** Downloads and metadata are visible to the owning workflow's full workflow family. */
    private @NonNull FileModel getFamilyAccessibleFile(String workflowId, String fileId) {
        FileModel model = getFileModelOrThrow(fileId);
        if (model.getWorkflowId() == null || model.getWorkflowId().isBlank()) {
            throw new AccessForbiddenException("File has no workflowId: " + fileId);
        }

        Set<String> family = workflowFamilyResolver.getFamily(workflowId);
        if (!family.contains(model.getWorkflowId())) {
            throw new AccessForbiddenException("Workflow cannot access file: " + fileId);
        }
        return model;
    }

    private FileModel getFileModelOrThrow(String fileId) {
        FileModel model = fileMetadataDAO.getFileMetadata(fileId);
        if (model == null) {
            throw new NotFoundException("File not found: " + fileId);
        }
        return model;
    }
}

View on GitHub (pinned to cf7c3e4a8a)