Skip to content

[Bug]: Unarchive does not process folder template variables in tasksFolder (literal placeholder folder created) #1872

@OXI-717

Description

@OXI-717

Versions

  • TaskNotes: 4.5.3
  • Obsidian: 1.6.7
  • OS: macOS (Darwin 24.6.0)

Summary

When moveArchivedTasks: true and tasksFolder contains a template placeholder such as {{project}}, calling unarchive (via POST /api/tasks/:id/archive on an archived task, or the equivalent UI/context-menu action) moves the file into a folder where the placeholder is left literal instead of being rendered.

The archive direction works correctly because that branch calls processFolderTemplate(...). The unarchive branch does not.

Settings to reproduce

{
  "tasksFolder": "_PROJECTS/{{project}}/tasks",
  "archiveFolder": "_PROJECTS/{{project}}/tasks/archive",
  "moveArchivedTasks": true
}

Repro

  1. Create a task with a project field set, e.g.:

    curl -X POST http://localhost:8080/api/tasks \
      -H "Content-Type: application/json" \
      -d '{"title":"repro","projects":["[[_PROJECTS/Demo/Demo]]"]}'

    File correctly lands at _PROJECTS/Demo/tasks/repro.md (template rendered).

  2. Archive the task:

    curl -X POST "http://localhost:8080/api/tasks/_PROJECTS%2FDemo%2Ftasks%2Frepro.md/archive"

    File correctly moves to _PROJECTS/Demo/tasks/archive/repro.md.

  3. Unarchive (toggle archive endpoint again on the archived path):

    curl -X POST "http://localhost:8080/api/tasks/_PROJECTS%2FDemo%2Ftasks%2Farchive%2Frepro.md/archive"

    ❌ File ends up at _PROJECTS/{{project}}/tasks/repro.md — a literal {{project}} directory is created on disk.

Expected

File should be moved back to _PROJECTS/Demo/tasks/repro.md — same template rendering applied as on archive.

Source pointer

In src/services/TaskService.ts, function toggleArchive, around the file move section:

  • Archive branch (lines ~972–982 on main): calls processFolderTemplate(archiveFolderTemplate, { title, priority, status, contexts, projects }) — placeholders are rendered.
  • Unarchive branch (lines ~1007–1015 on main): uses this.plugin.settings.tasksFolder.trim() directly with no template processing, then builds ${tasksFolder}/${file.name} from the raw template.

Suggested fix

Mirror the archive branch — render the template before computing the destination path:

} else if (isCurrentlyArchived && this.plugin.settings.tasksFolder?.trim()) {
    const tasksFolderTemplate = this.plugin.settings.tasksFolder.trim();
    const tasksFolder = this.processFolderTemplate(tasksFolderTemplate, {
        title: updatedTask.title || "",
        priority: updatedTask.priority,
        status: updatedTask.status,
        contexts: updatedTask.contexts,
        projects: updatedTask.projects,
    });
    await ensureFolderExists(this.plugin.app.vault, tasksFolder);
    const newPath = `${tasksFolder}/${file.name}`;
    // ... rest unchanged
}

Workaround

Until fixed, automation calling the archive endpoint should perform unarchive via direct file move (rendering the template client-side) instead of relying on POST /api/tasks/:id/archive for the unarchive transition.

Notes

  • Reproduces consistently across Obsidian/TaskNotes restarts.
  • Not specific to nested archive folder layout — same behavior with sibling archive folders, as long as tasksFolder template uses any placeholder.
  • Discovered while building a TaskNotes CLI wrapper that talks to the HTTP API; verified by reading toggleArchive in src/services/TaskService.ts.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions