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
-
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).
-
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.
-
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.
Versions
Summary
When
moveArchivedTasks: trueandtasksFoldercontains a template placeholder such as{{project}}, calling unarchive (viaPOST /api/tasks/:id/archiveon 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
Create a task with a project field set, e.g.:
File correctly lands at
_PROJECTS/Demo/tasks/repro.md(template rendered).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.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, functiontoggleArchive, around the file move section:main): callsprocessFolderTemplate(archiveFolderTemplate, { title, priority, status, contexts, projects })— placeholders are rendered.main): usesthis.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:
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/archivefor the unarchive transition.Notes
tasksFoldertemplate uses any placeholder.toggleArchiveinsrc/services/TaskService.ts.