Introduced in v7.1.0 - Automatically track work session duration and log hours to HacknPlan without manual calculation.
- Quick Start
- How It Works
- Configuration
- Usage Examples
- Edge Cases & Behavior
- Best Practices
- Troubleshooting
// 1. Start working on a task
await callTool('start_task', {
workItemId: 123,
comment: 'Starting implementation of OAuth authentication'
});
// ✅ Session started
// 2. Work on the task for 2.5 hours...
// 3. Complete the task
await callTool('complete_task', {
workItemId: 123,
comment: 'OAuth complete. All tests passing.'
});
// ✅ Automatically logged 2.5h to HacknPlanThat's it! No manual time calculation required.
- 🕒 Automatic Duration Tracking - Records start time, calculates elapsed time on completion
- 💾 Persistent Storage - Sessions survive server restarts via
.cache/time-tracking.json - 🔒 Session Limits - 4-hour maximum for automatic logging (configurable)
- 📊 Multiple Concurrent Tasks - Track several tasks independently
- ⚡ Zero Configuration - Works automatically when using workflow shortcuts
When you call start_task, the server:
- Records the current timestamp (
Date.now()) in the time tracking cache - Persists the entry to
.cache/time-tracking.json(survives restarts) - Marks the session as
'active'
await callTool('start_task', {
workItemId: 123,
comment: 'Starting implementation of user authentication'
});
// ✅ Time tracking session started at 2025-12-21T10:30:00ZBehind the scenes:
{
"version": 1,
"lastSaved": "2025-12-21T10:30:00.000Z",
"entries": [
{
"projectId": 230954,
"workItemId": 123,
"startedAtMs": 1734781800000,
"lastActivityAtMs": 1734781800000,
"accumulatedHours": 0,
"status": "active"
}
]
}The tracking session remains active while you work. The cache:
- ✅ Persists to disk every 100ms after changes (debounced)
- ✅ Performs periodic saves every 5 minutes
- ✅ Survives server restarts (auto-loads on first access)
- ✅ Has 7-day TTL for active entries
When you call complete_task without hoursWorked:
- Retrieves the active session from cache
- Calculates elapsed time:
Date.now() - startedAtMs - Converts to hours:
elapsedMs / 3600000 - Checks 4-hour limit (configurable via
MAX_AUTO_LOG_HOURS) - If within limit: automatically logs the hours to HacknPlan
- If exceeded: returns warning, requires manual
hoursWorked
// Scenario: 2.5 hours elapsed since start_task
await callTool('complete_task', {
workItemId: 123,
comment: 'Authentication complete, all tests passing'
});
// Response:
{
"workItem": { ... },
"workSession": {
"hours": 2.5,
"date": "2025-12-21"
},
"timeTracking": {
"autoLogged": true,
"hours": 2.5,
"elapsedMs": 9000000,
"elapsedFormatted": "2h 30m"
},
"success": true
}
// ✅ Automatically logged 2.5h to HacknPlan| Variable | Default | Description |
|---|---|---|
MAX_AUTO_LOG_HOURS |
4 |
Maximum hours for automatic logging. Sessions exceeding this require manual hoursWorked parameter. |
HACKNPLAN_TIME_TRACKING_CACHE_SIZE |
500 |
Maximum number of tracked sessions in cache (LRU eviction). |
Why 4-hour limit? Prevents accidental logging of unrealistic time if you forget to complete a task overnight or over a weekend.
# In ~/.claude.json
{
"mcpServers": {
"hacknplan": {
"env": {
"MAX_AUTO_LOG_HOURS": "8",
"HACKNPLAN_TIME_TRACKING_CACHE_SIZE": "1000"
}
}
}
}Or via shell:
export MAX_AUTO_LOG_HOURS=8
export HACKNPLAN_TIME_TRACKING_CACHE_SIZE=1000
node dist/index.js- Location:
.cache/time-tracking.json(relative to server working directory) - Format: JSON with schema version for future migrations
- Write Strategy: Atomic writes (
.tmp→ rename) for crash safety - Auto-Save: Debounced 100ms after modifications
- Periodic Save: Every 5 minutes (prevents data loss during light activity)
- Load Strategy: Non-blocking on first cache access
| Status | TTL | Purpose |
|---|---|---|
| Active | 7 days | Allows resuming long-running tasks |
| Completed | 30 days | Keeps history for reports/auditing |
Expired entries are automatically removed when accessed.
// 1. Start working on a task
await callTool('start_task', {
workItemId: 123,
comment: 'Starting implementation using bcrypt + nodemailer'
});
// ✅ Session started
// ... work on the task for 3 hours ...
// 2. Complete the task (auto-logs 3 hours)
await callTool('complete_task', {
workItemId: 123,
comment: 'Password reset complete. Email delivery verified.'
});
// ✅ Automatically logged 3hUse hoursWorked to bypass auto-tracking and specify exact hours:
// Scenario: Long session (>4h) or need specific hours
await callTool('complete_task', {
workItemId: 123,
hoursWorked: 6.5, // Override auto-calculation
comment: 'Complex refactoring completed'
});
// ✅ Logs exactly 6.5h (ignores tracked session)The cache supports multiple work sessions on the same task:
// Day 1: Work for 2 hours
await callTool('start_task', { workItemId: 123 });
// ... 2 hours later ...
await callTool('complete_task', { workItemId: 123 });
// ✅ Logged 2h
// Day 2: Resume work for another 3 hours
await callTool('start_task', { workItemId: 123 }); // Reactivates entry
// ... 3 hours later ...
await callTool('complete_task', { workItemId: 123 });
// ✅ Logged 3h (total: 5h across both sessions)Note: Accumulated hours are tracked in the cache entry's accumulatedHours field but each complete_task only logs the current session duration.
// Scenario: Started task, worked for 5 hours (exceeds 4h limit)
await callTool('complete_task', {
workItemId: 123
// No hoursWorked parameter
});
// Response:
{
"workItem": { "isCompleted": true, ... },
"timeTracking": {
"autoLogged": false,
"elapsedMs": 18000000,
"elapsedFormatted": "5h 0m",
"limitExceeded": true,
"warning": "Elapsed time 5h 0m exceeds 4h limit. Task completed without auto-logging hours."
},
"success": true
}
// ⚠️ Task completed but NO hours logged (manual log required)Fix:
// Manually log the hours
await callTool('log_work_session', {
workItemId: 123,
hours: 5,
description: 'Complex bug investigation and fix'
});For tasks spanning multiple days, complete and restart daily for accurate per-day tracking:
// Day 1: Work 3 hours
await callTool('start_task', { workItemId: 123 });
// ... 3 hours later ...
await callTool('complete_task', { workItemId: 123 }); // Logs 3h on Day 1
// Day 2: Work 4 hours
await callTool('start_task', { workItemId: 123 });
// ... 4 hours later ...
await callTool('complete_task', { workItemId: 123 }); // Logs 4h on Day 2
// Day 3: Finish task (2 hours)
await callTool('start_task', { workItemId: 123 });
// ... 2 hours later ...
await callTool('complete_task', {
workItemId: 123,
moveToStage: 4 // Move to "Complete" stage
}); // Logs 2h on Day 3, marks task completeBenefit: Accurate per-day time logs, better reporting.
Track multiple tasks independently:
// Start both tasks
await callTool('start_task', { workItemId: 123 }); // Backend task
await callTool('start_task', { workItemId: 456 }); // Frontend task
// Complete backend (logs time for task 123 only)
await callTool('complete_task', { workItemId: 123 });
// Complete frontend (logs time for task 456 only)
await callTool('complete_task', { workItemId: 456 });Each task maintains its own independent session timer.
Behavior: Resets the session timer to current time.
await callTool('start_task', { workItemId: 123 }); // Started at 10:00 AM
// ... user interruption ...
await callTool('start_task', { workItemId: 123 }); // Restarted at 2:00 PM
// ✅ Previous session discarded, new start time is 2:00 PMUse Case: Handling interruptions or switching context.
Behavior: No auto-logging occurs (no error thrown).
// Never called start_task
await callTool('complete_task', {
workItemId: 123,
comment: 'Task completed'
// No hoursWorked
});
// Response:
{
"workItem": { "isCompleted": true, ... },
"timeTracking": {
"autoLogged": false
// No hours, no warning
},
"success": true
}
// ✅ Task completed, but no hours loggedFix: Either call start_task first, or provide manual hoursWorked.
Behavior: Session persists and resumes after restart.
// Before restart:
await callTool('start_task', { workItemId: 123 }); // 10:00 AM
// *** Server crashes and restarts at 10:30 AM ***
// After restart:
await callTool('complete_task', { workItemId: 123 }); // 11:00 AM
// ✅ Auto-logs 1h (elapsed time from original start at 10:00 AM)Implementation: Cache auto-loads from .cache/time-tracking.json on first access.
Behavior: Least recently used entries evicted when cache reaches capacity.
- Capacity: 500 entries by default (
HACKNPLAN_TIME_TRACKING_CACHE_SIZE) - Eviction: Oldest entry (first in Map iteration order)
- Warning: No warning logged for evicted entries
Mitigation:
- Increase
HACKNPLAN_TIME_TRACKING_CACHE_SIZEfor larger teams - Complete tasks regularly to free up cache space
- Eviction primarily affects very old or inactive entries
Behavior: All timestamps use UTC milliseconds (Date.now()).
startedAtMs: UTC timestamp when session startedlastActivityAtMs: UTC timestamp of last activity- Work log date: Server's local date when
complete_taskcalled
Example:
// Server in PST (UTC-8), task started at 5:00 PM local time
await callTool('start_task', { workItemId: 123 });
// Stores: startedAtMs = 1734825600000 (2025-12-21T01:00:00Z UTC)
// Completed next day at 9:00 AM local time (4h elapsed)
await callTool('complete_task', { workItemId: 123 });
// Logs: 4h on date 2025-12-21 (server local date)Use Auto-Tracking (no hoursWorked):
- ✅ Focused work sessions under 4 hours
- ✅ Uninterrupted task completion
- ✅ Real-time tracking for accurate hours
- ✅ Simple "start → work → complete" workflows
Use Manual Hours (explicit hoursWorked):
- ✅ Sessions exceeding 4 hours
- ✅ Multi-day tasks with scattered work
- ✅ Retrospective time logging (completing tasks from memory)
- ✅ Batch completions where exact hours are known
Option A: Restart the session
// Started at 10 AM, interrupted at 11 AM
await callTool('start_task', { workItemId: 123 }); // Resets timer
// Resume at 2 PM
await callTool('start_task', { workItemId: 123 }); // Resets again
// Complete at 4 PM
await callTool('complete_task', { workItemId: 123 });
// ✅ Logs 2h (only counts 2 PM → 4 PM)Option B: Complete and restart
// First session: 10 AM - 11 AM
await callTool('complete_task', { workItemId: 123 }); // Logs 1h
// Second session: 2 PM - 4 PM
await callTool('start_task', { workItemId: 123 });
await callTool('complete_task', { workItemId: 123 }); // Logs 2h- Check active sessions: Use
get_my_current_tasksto see tasks with active tracking - Avoid multiple active sessions: Complete or abandon old sessions before starting new ones
- Use comments: Add context when starting/completing tasks for better work history
- Monitor limits: If frequently hitting 4h limit, consider increasing
MAX_AUTO_LOG_HOURS
The cache is self-maintaining, but you can monitor it:
- Active entries expire after 7 days - Long-running tasks should be completed regularly
- Completed entries expire after 30 days - Historical data eventually purges
- LRU eviction at capacity - Increase
HACKNPLAN_TIME_TRACKING_CACHE_SIZEfor larger teams
No manual cleanup required - the cache handles expiration and eviction automatically.
| Issue | Cause | Solution |
|---|---|---|
Hours not logged on complete_task |
No prior start_task call |
Call start_task before working, or provide hoursWorked |
| "Exceeds 4h limit" warning | Session longer than MAX_AUTO_LOG_HOURS |
Increase env var or use manual hoursWorked parameter |
| Session lost after restart | Disk write failure (ENOSPC, permissions) | Check .cache/ directory permissions and disk space |
| Incorrect elapsed time | Multiple start_task calls reset timer |
Use single start_task per session, or track time externally |
| Cache file corrupted | Server crash during write | File auto-backed up (.corrupt-<timestamp>), cache starts fresh |
Q: Hours not logged on complete_task?
- Ensure you called
start_taskfirst - Check if session exceeded 4h limit (use manual
hoursWorkedif so) - Verify task wasn't completed without time tracking enabled
Q: Time seems incorrect?
- Time calculates from last
start_taskcall (not first) - Calling
start_taskmultiple times resets the timer - Check
.cache/time-tracking.jsonfor session details
Q: Session lost after restart?
- Check
.cache/directory exists and is writable - Verify disk space (auto-save fails on ENOSPC)
- Check file permissions for
.cache/time-tracking.json
Q: How do I see active sessions?
- Use
get_my_current_tasksto see tasks you're currently working on - Check
.cache/time-tracking.jsondirectly for all active sessions
For workflows that don't fit automatic tracking:
// Log time without starting/completing
await callTool('log_work_session', {
workItemId: 123,
hours: 2.5,
date: '2025-12-20', // Optional: defaults to today
description: 'Implemented feature X and wrote tests'
});
// ✅ Logs 2.5h, task remains in current stateThis is useful for:
- Retrospective time logging
- Adjusting previously logged hours
- Logging time without changing task status
For developers integrating or debugging:
Source Files:
src/cache/time-tracking-cache.ts- LRU cache with persistencesrc/types/time-tracking.ts- Type definitions and constantssrc/tools/workflow.ts-start_taskandcomplete_taskhandlers
Cache Schema:
interface TimeTrackingEntry {
projectId: number;
workItemId: number;
startedAtMs: number; // Date.now() when session started
lastActivityAtMs: number; // Updated on each activity
accumulatedHours: number; // Sum of logged hours across sessions
status: 'active' | 'completed';
}Persistence Strategy:
- Atomic writes:
.cache/time-tracking.json.tmp→ rename to.cache/time-tracking.json - Debounced auto-save: 100ms after modifications
- Periodic save: Every 5 minutes
- Crash recovery: Auto-loads on server start
See Also:
- Workflow Shortcuts -
start_task,complete_taskAPI - Work Items - Task management
- Best Practices - Time tracking recommendations