fix(core): preserve per-task tokenUsage across archival (#1908)
## The bug Per-task token usage (and the cost figures derived from it) is **silently dropped the moment a task is archived**. A task that burned millions of tokens shows `0` — or nothing — everywhere once it leaves the live board. Root cause is a field whitelist plus a missing type field: - `TaskStore.taskToArchiveEntry()` (`packages/core/src/store.ts`) constructs the archived record from an **explicit property whitelist**. It copies `modelId` / `modelProvider` / `planningModelId` / … but never `task.tokenUsage`. - `ArchivedTaskEntry` (`packages/core/src/types.ts`) has no `tokenUsage` field, so even a stray copy would be dropped by the type. At archival time the task is DB-hydrated and still carries `tokenUsage`, and the live `tasks` row (with its `tokenUsage*` columns) is then deleted — so the whitelist is the only place the data survives or dies. Result: `archive.db` (`archived_tasks.taskJson`) never contains token stats. Any tool that reports token/cost usage can only ever see the small live working set, never the hundreds of finished tasks. ## The fix Thread `tokenUsage` through the archive round-trip (5 lines, all pass-through of the already-typed `TaskTokenUsage`): - `ArchivedTaskEntry` gains an optional `tokenUsage?: TaskTokenUsage` field. - `taskToArchiveEntry()` copies `tokenUsage: task.tokenUsage` (write path). - `archiveEntryToTask()` and `unarchiveTask()` copy `tokenUsage: entry.tokenUsage` (both restore paths), so restored tasks keep their history too. Because the archived entry is serialized into `taskJson`, no DB migration/column is needed — the counts land in the existing JSON blob and read back via `json_extract(taskJson, '$.tokenUsage.totalTokens')` (and the `inputTokens` / `outputTokens` / `cachedTokens` / `cacheWriteTokens` breakdown). ## Verification Applied the equivalent change to the bundled `dist/bin.js` on a live install and archived a 9.9M-token task into an isolated copy of the store. The full breakdown survived into `archive.db`: ``` inputTokens=119 outputTokens=26100 cachedTokens=9637483 cacheWriteTokens=233674 totalTokens=9897376 modelId=claude-sonnet-4-6 (+ per-model split intact) ``` Without the change the same archival leaves `tokenUsage` absent from the entry. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Archived tasks now keep token usage details when saved and restored, so task history remains accurate across archive flows. * Restored archived tasks now display the same usage accounting they had before being archived. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -2331,6 +2331,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
validatorModelId: entry.validatorModelId,
|
||||
planningModelProvider: entry.planningModelProvider,
|
||||
planningModelId: entry.planningModelId,
|
||||
tokenUsage: entry.tokenUsage,
|
||||
breakIntoSubtasks: entry.breakIntoSubtasks,
|
||||
noCommitsExpected: entry.noCommitsExpected,
|
||||
branchContext: entry.branchContext,
|
||||
@@ -2470,6 +2471,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
validatorModelId: task.validatorModelId,
|
||||
planningModelProvider: task.planningModelProvider,
|
||||
planningModelId: task.planningModelId,
|
||||
tokenUsage: task.tokenUsage,
|
||||
breakIntoSubtasks: task.breakIntoSubtasks,
|
||||
noCommitsExpected: task.noCommitsExpected,
|
||||
baseBranch: task.baseBranch,
|
||||
@@ -14919,6 +14921,7 @@ ${TASK_UPSERT_SQL_ASSIGNMENTS}
|
||||
validatorModelId: entry.validatorModelId,
|
||||
planningModelProvider: entry.planningModelProvider,
|
||||
planningModelId: entry.planningModelId,
|
||||
tokenUsage: entry.tokenUsage,
|
||||
breakIntoSubtasks: entry.breakIntoSubtasks,
|
||||
noCommitsExpected: entry.noCommitsExpected,
|
||||
modifiedFiles: entry.modifiedFiles,
|
||||
|
||||
@@ -4955,6 +4955,8 @@ export interface ArchivedTaskEntry {
|
||||
/** Optional: planning model override for triage agent */
|
||||
planningModelProvider?: string;
|
||||
planningModelId?: string;
|
||||
/** Per-task token/cost accounting (input/output/cache) preserved across archival. */
|
||||
tokenUsage?: TaskTokenUsage;
|
||||
/** Optional: other metadata to preserve */
|
||||
breakIntoSubtasks?: boolean;
|
||||
noCommitsExpected?: boolean;
|
||||
|
||||
Reference in New Issue
Block a user