Skip to content

Instantly share code, notes, and snippets.

@wimpunk
Forked from djmitche/@@INTRO.md
Last active January 31, 2025 08:14
Show Gist options
  • Save wimpunk/947572bc932635a5d13550179d971b86 to your computer and use it in GitHub Desktop.
Save wimpunk/947572bc932635a5d13550179d971b86 to your computer and use it in GitHub Desktop.
My use of Taskwarrior

Getting Started

My Usage

I've been using this for almost ten years now, so here are some of the ways I have set it up to be most productive. See my taskrc below for implementation details.

In general, I've had the most success by keeping lists of tasks short and to the point, avoiding the anxiety of seeing 100 tasks and feeling like I'm going to drown. It serves three purposes:

  • Ensures I do the things I'm responsible for
  • Keeps me from getting anxious about the things I'm responsible for
  • Helps me to not get lost focusing on one top-priority thing at the expense of others

Trust (but occasionally verify) Taskwarrior to keep things in order. Plan the work, work the plan.

This is basically the "Getting Things Done" approach: filter everything into a list, spending minimal time on tasks until they reach the top of the list. It means I can skim through my email quickly without worrying that I'll miss or forget something, and without feeling like I need to handle any of it "right now".

I use Taskwarrior at work, with exclusively work-related tasks, as well as for my non-work life. The latter includes just about everything I need to get done, and is synchronized across all of my devices, so I can access it from wherever I am.

Task Views and Aliases

I keep a shell open just for use with Taskwarrior (and for running tmux commands and other miscellaneous stuff). I use tt (source below) for a quick way to show the current tasklist. Just running tt will re-display the last tasklist. Running tt <name> will run a particular query, e.g., tt triage.

This also adds a shell prompt that displays the currently active tasks in the terminal title.

today

Most of the time I show the today view. This view is designed to just show me actionable tasks, in order of urgency. It filters out +BLOCKED tasks, tasks waiting on +review, and tasks that are not due soon. It includes tasks marked +respond (such as CLs requiring my review), +today, +next, +inprogress, or +yesterday.

I use the "urgency" feature of Taskwarrior to sort this list with the most urgent things at the top -- typically reviews and time-sensitive items. But, the list is short enough that I can skim it throughout the day and pick the most appropriate task for the moment.

Special Tags

Taskwarrior automatically recognizes the +next tag and massively increases the priority of tagged tasks. I use this frequently for things I need to accomplish immediately, before any other work. For example, if someone pings me in irc with a request or I agree to do something in a meeting, I'll often add it with task add +next 'whatever'. Usually that's a quick thing, perhaps involving creating a bug or sending an email. Otherwise, I can adjust its Taskwarrior properties after the meeting and remove the +next tag.

When I have completed a task and put it up for review, I tag it +review. This makes the task disappear from the default view. Once the review is complete, Bugwarrior will delete the task. Otherwise, I'll catch it in a weekly triage.

If I've started a task and will benefit from finishing it sooner rather than later, I'll tag it +inprogress. This bumps the urgency a bit and ensures it appears in the today view. Bugwarrior is configured to set this automatically for Chromium bugs marked "Started".

I tag anything requiring me to respond to someone else as +respond. This is automatically added by Bugwarrior for review requests.

I use the +today tag for things I should do today. Most mornings, I'll skim the full task list and add +today to a few tasks that I would like to get to. I have a crontask which removes the +today tag and replaces it with +yesterday every night. The +yesterday tasks still show up in my default view, inviting me to re-evaluate their urgency.

I use +plane for ideas of unimportant things that might make for good hacking on a plane -- pretty clear what to do, and easily completed in a short time.

Dependencies and Waiting

When a task bubbles up and I think "huh, I can't work on that right now", I generally try to make it disappear. One way to do this is to indicate that it depends on another task assigned to me (task 123 modify depends:99). However, if it depends on something that Taskwarrior is not tracking (such as a bug assigned to someone else), I will set it to wait and appropriate length of time (usually a few days to a week - task 123 modify wait:1wk).

Recurring Triage

Trust, but verify. I don't want to miss something just because I forgot to tag it correctly or mistyped a task number. So I have weekly recurring tasks to triage my task list. To accomplish this, I look at all of the tasks in the project (task triage) and just scan the list, looking for anything surprising.

Surprises might be of the form "oh, that's important!" in which case I'll adjust tags, priority, etc. as necessary. Or they might be of the form "I thought that was done" -- usually a CL I've put out for review, or a note to myself that I remembered without Taskwarrior's help.

Syncs to External Systems

I synchronize tasks from bugs.chromium.org, chromium-review.googlesource.com, and github.com, using Bugwarrior.

For Chromium bugs, every bug assigned to me gets a task, with "Started" status adding the +inprogress tag. This means all of my bugs are in Taskwarrior, and those which I've marked "Started" appear in my day-to-day view. One issue with this approach is the common practice of assigning a bug to someone else when asking a question: Bugwarrior dutifully deletes the corresponding task, and if that person never answers the question I may completely forget about the bug. Note that I had to hack Bugwarrior pretty substantially to get this to work, as there is no stable API for crbugs.

The Gerrit sync largely mirrors the Gerrit attention bit. CLs that require my attention are included, with +inprogress for CLs I own, and +respond for CLs I do not own. CLs I own that are waiting for review are tagged +review.

I sync both GitHub issues and pull requests from Google-related orgs. Like CLs, issues show up as tasks, and PRs requiring my review are tagged +respond.

Taskwarrior Features

Separate from how I do things, here are some of the more useful features of Taskwarrior:

Tags

Use tags to identify categories of tasks and make them easier to filter, or to affect urgency.

Recurrence and Dependencies

Set up recurring tasks for things you need to do regularly, such as triage or writing a snippet for the week.

Dependencies beteween tasks need some additional setup to be useful, but can help hide tasks from view until they are actionable.

Urgency

Urgency is awesome, once you get it tuned. Task lists are displayed in order by urgency, so you can just work on the top task in the list at any one time.

Urgency calculations are based on priority, tags, dependencies, time since the task was created, and a number of other attributes. You can add custom urgencies for tags.

Annotations

Annotations are extra, datestamped bits of information on a task. For tasks synced from somewhere else, these aren't especially useful (and I disable them). But for tasks that involve a lot of phone calls, emails, and waiting, it can be really useful to keep a tiny log of what's happened so far:

task 123 start
Starting task 123 fix Azure billing email
# ..email Azure help..
task 123 annotate "emailed help address"
task 123 stop wait:2d

Bugwarrior

Bugwarrior syncs Taskwarrior to other services. Set it up to run in crontask (I run it once an hour). See the attached bugwarriorrc for details on how I've configured these.

Bugzilla

Bugwarrior can sync any Bugzilla query. However, syncing the same bug with two different Bugwarrior configs will cause issues, so structure your queries carefully.

Github

Bugwarrior can sync both Github issues and PRs.

Phabricator

This sync is not very good -- Phabricator's API is lame (and in particular has no way to show you requested reviews) so Bugwarrior does what it can.

Crbugs

This sync is kind of a hack and based on scraping some HTML, as there is no stable API for crbugs.

Gerrit

Bugwarrior can sync CLs from Gerrit, with pretty flexible queries.

Sync

Taskwarrior supports synchronizing your tasklist across multiple systems, in an offline manner. It's a little difficult to set up (the taskserver is undocumented). I'm working on fixing this, but step 1 is "rewrite it in Rust" and the Taskwarrior development community is pretty small, so it's not going quickly.

You will generate some keys that you need to distribute to all sync'd instances of Taskwarrior. Configure them as instructed.

Then run task sync init on the system that has all of your tasks on it. On the other systems, just run task sync.

Synchronization occurs by tracking changes made on each system and applying those changes on other systems. This does a pretty good job of "merging" changes, even to the same task.

NOTE: if you set up sync, be sure that only one host handles recurrence, otherwise you will get multiple copies of recurring tasks. I accomplish this with an Ansible conditional in the taskrc template below.

Mobile

https://play.google.com/store/apps/details?id=kvj.taskw

This is a thin wrapper around the command-line tool, and will require some use of adb to set it up (copying the sync keys there). Sync setup instructions are here.

[general]
# Specify which TaskRC configuration file to use.
taskrc = ~/.taskrc
# Set to true to shorten links.
shorten = False
# When false, links are appended as an annotation.
inline_links = True
# When true will include a link to the ticket as an annotation.
annotation_links = False
# When false skips putting issue comments into annotations.
annotation_comments = True
# When false strips newlines from comments in annotations.
annotation_newlines = False
# Set to one of DEBUG, INFO, WARNING, ERROR, CRITICAL, or DISABLED to
# control the logging verbosity.
log.level = INFO
# log.level = DEBUG
# Set to the path at which you would like logging messages written.
# log.file = /tmp/bugerror
# Import maximally this number of characters of incoming annotations.
annotation_length = 45
# Use maximally this number of characters in the description.
description_length = 35
# If false, bugwarrior won't bother with adding annotations to your tasks at all.
#merge_annotations = True
merge_annotations = false
# If false, bugwarrior won't bother with adding tags to your tasks at all.
merge_tags = True
# If true, bugwarrior will delete all tags prior to fetching new ones,
# except those listed in static_tags. Only work if merge_tags is true.
replace_tags = False
# A list of tags that shouldn't be *removed* by bugwarrior. Use for tags
# that you want to keep when replace_tags is set to true.
static_tags = next, today, yesterday, contrib, work, backlog
# A list of attributes that shouldn't be *updated* by bugwarrior. Use for
# values that you want to tune manually. Note that service-specific UDAs
# can be included here.
static_fields = priority, project
targets = dataflow, karaba
#targets = dataflow, karaba, gmail
#targets = gmail
[dataflow]
service = azuredevops
# valid untill 15-12-2024
# See https://bugwarrior.readthedocs.io/en/latest/common_configuration.html#password-management
# for more info.
# TODO: document where to find the PAT
ado.PAT = @oracle:use_keyring
ado.organization = 24sea
ado.project = Dataflow
ado.add_tags = dataflow, work
ado.description_template = [ADO-{{adoid}}] {{adotitle}}
ado.project_template = {{project|replace(".","-")}}
ado.wiql_filter = [System.AssignedTo] = @me and [System.TeamProject] = 'Dataflow' AND ( [System.State] = 'Active' or [System.State] = 'New')
[karaba]
service = azuredevops
# valid untill 15-12-2024
# See https://bugwarrior.readthedocs.io/en/latest/common_configuration.html#password-management
# for more info.
ado.PAT = @oracle:use_keyring
ado.organization = 24sea
ado.project = Karaba
ado.add_tags = karaba, work
ado.description_template = [ADO-{{adoid}}] {{adotitle}}
ado.project_template = {{project|replace(".","-")}}
# ado.wiql_filter = [System.AssignedTo] = @me
ado.wiql_filter = [System.AssignedTo] = @me and [System.TeamProject] = 'Karaba' AND ([System.State] = 'Active' or [System.State] = 'New')
[gmail]
service = gmail
gmail.query = label:_todo
gmail.login_name = [email protected]
gmail.add_tags = inbox
#!/usr/bin/python3
###############################################################################
#
# Copyright 2016 - 2021, Thomas Lauf, Paul Beckingham, Federico Hernandez.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# https://www.opensource.org/licenses/mit-license.php
#
###############################################################################
from __future__ import print_function
import json
import subprocess
import sys
# Hook should extract all of the following for use as Timewarrior tags:
# UUID
# Project
# Tags
# Description
# UDAs
try:
input_stream = sys.stdin.buffer
except AttributeError:
input_stream = sys.stdin
# Make no changes to the task, simply observe.
old = json.loads(input_stream.readline().decode("utf-8", errors="replace"))
new = json.loads(input_stream.readline().decode("utf-8", errors="replace"))
print(json.dumps(new))
def replacechars(input_str):
return input_str.replace("#","-").replace("@","-")
def extract_tags_from(json_obj):
# Extract attributes for use as tags.
desc = json_obj['description']
tags = [replacechars(desc)]
if 'project' in json_obj:
tags.append(json_obj['project'])
if 'tags' in json_obj:
tags.extend(json_obj['tags'])
for tag in tags:
if (tag == "today" or tag == "tomorrow"):
tags.remove(tag)
# remove duplicates
tags2 = list(set(tags))
return tags2
def extract_annotation_from(json_obj):
if 'annotations' not in json_obj:
return '\'\''
return json_obj['annotations'][0]['description']
start_or_stop = ''
# Started task.
if 'start' in new and 'start' not in old:
start_or_stop = 'start'
# Stopped task.
elif ('start' not in new or 'end' in new) and 'start' in old:
start_or_stop = 'stop'
if start_or_stop:
tags = extract_tags_from(new)
print("Checking start or stop")
print(['echo', start_or_stop] + tags + [':yes'])
subprocess.call(['echo', start_or_stop] + tags + [':yes'])
subprocess.call(['timew', start_or_stop] + tags + [':yes'])
# Modifications to task other than start/stop
elif 'start' in new and 'start' in old:
print("Start in new")
old_tags = extract_tags_from(old)
new_tags = extract_tags_from(new)
if old_tags != new_tags:
subprocess.call(['timew', 'untag', '@1'] + old_tags + [':yes'])
subprocess.call(['timew', 'tag', '@1'] + new_tags + [':yes'])
old_annotation = extract_annotation_from(old)
new_annotation = extract_annotation_from(new)
if old_annotation != new_annotation:
subprocess.call(['timew', 'annotate', '@1', new_annotation])
#! /bin/bash
cd ~
search_file=".task/task-pane-search"
refresh_file=".task/task-pane-refresh"
output_file=".task/task-pane-output"
inputs=".task/*.data $search_file $refresh_file"
task_pane=$(readlink -f ~/bin/task-pane)
this_pane=$(tmux list-panes -F "#{pane_id} #{pane_start_command}" | grep "^%[0-9]* $task_pane$" | cut -d' ' -f 1)
[ -f "$search_file" ] || touch "$search_file"
[ -f "$refresh_file" ] || touch "$refresh_file"
(
echo "refresh"
while true; do
inotifywait -q -q -e modify $inputs
echo "refresh"
done
# pipe these two subshells together so that a line from the first leads to a loop of the second, and
# those queue up such as when several inotifywait changes occur.
) | (
while read _; do
# script does not copy the terminal column width, so set it manually
cols=`tput cols`
echo -n $'\033[H..'
script -q -c "stty cols $cols; task rc.gc=no rc.indent.report=4 rc.verbose= $(< $search_file)" $output_file >/dev/null </dev/null
clear
cat $output_file | grep -v '^Script' | grep -v '^$'
lines=$(wc -l < $output_file)
tmux resize-pane -t $this_pane -y $(( lines - 2 ))
tmux set set-titles-string " $(task rc.gc=no rc.indent.report=4 rc.verbose= rc.report.next.columns=description.desc rc.report.next.labels= rc.defaultwidth=1000 next +ACTIVE 2>/dev/null </dev/null | sed -e 's/^ */«/' | sed 's/$/»/' | tr '\n' ' ')"
done
)
# Files
data.location=~/.task
color=on
color.header=rgb031
color.footnote=rgb031
color.error=rgb031
color.debug=rgb031
color.summary.bar=white on rgb030
color.summary.background=white on color0
color.history.add=color0 on rgb010
color.history.done=color0 on rgb030
color.history.delete=color0 on rgb050
color.burndown.pending=on rgb010
color.burndown.started=on rgb030
color.burndown.done=on gray4
color.sync.added=gray4
color.sync.changed=rgb030
color.sync.rejected=red
color.undo.before=rgb031
color.undo.after=rgb053
color.calendar.today=color0 on rgb151
color.calendar.due=color0 on color249
color.calendar.due.today=color0 on color225
color.calendar.overdue=color0 on color255
color.calendar.weekend=on color235
color.calendar.holiday=rgb151 on rgb020
color.calendar.weeknumber=rgb010
color.recurring=rgb151
color.overdue=color255
color.due.today=color252
color.due=color249
color.active=bold white on rgb012
color.uda.priority.none=
color.uda.priority.H=rgb050
color.uda.priority.M=rgb040
color.uda.priority.L=rgb030
color.tagged=none
color.blocked=color249
color.blocking=rgb240
color.project.none=
color.tag.none=
color.alternate=on color233
color.tag.next=rgb252
color.tag.inprogress=rgb252
####
report.next.columns=id,project,priority,due,start.active,entry.age,urgency,description.desc,tags
report.next.labels=ID,Proj,Pri,Due,A,Age,Urg,Description,Tags
report.next.filter=status:pending -WAITING limit:10
report.short.columns=id,description.desc,project
report.short.labels=ID,Description,Project
report.short.filter=status:pending -WAITING limit:5
report.short.sort=urgency-
report.short.context=1
report.ready.columns=id,project,priority,due,start.active,entry.age,urgency,description,tags
report.ready.labels=ID,Proj,Pri,Due,A,Age,Urg,Description,Tags
report.triage.description=Personal - To-Do
report.triage.columns=id,priority,start.active,urgency,due,description.desc,tags
report.triage.labels=ID,Pri,A,Urg,Due,Description,Tags
report.triage.filter=( proj: or proj:personal ) ( due.before:tomorrow or due: ) status:pending -WAITING -idea
report.triage.sort=urgency-
report.today.description=Tasks for Today
report.today.columns=id,project,priority,start.active,urgency,due,description.desc,tags
report.today.labels=ID,Proj,Pri,A,Urg,Due,Description,Tags
report.today.filter=status:pending -BLOCKED -review and ( ( proj: and ( ( prio:H and due: ) or due.before:tomorrow or +respond or +today or +next or +inprogress or +yesterday) ) or +daytime )
report.today.sort=urgency-
report.active.description=Active Tasks
report.active.columns=id,description.desc,tags
report.active.labels=ID,Description,Tags
report.active.filter=status:pending +ACTIVE
report.active.sort=urgency-
uda.priority.default=M
priority.default=M
urgency.blocking.coefficient=0
urgency.annotations.coefficient=0
urgency.user.tag.respond.coefficient=10
urgency.user.tag.review.coefficient=-5
urgency.user.tag.inprogress.coefficient=2.5
#urgency.user.tag.today.coefficient=4
urgency.user.tag.today.coefficient=10
urgency.user.tag.yesterday.coefficient=3
urgency.user.tag.plane.coefficient=-2
urgency.user.tag.soon.coefficient=2
urgency.user.tag.later.coefficient=1
urgency.user.tag.wait.coefficient=-2
# Bugwarrior UDAs
uda.crbugsummary.type=string
uda.crbugsummary.label=Crbug Summary
uda.crbugurl.type=string
uda.crbugurl.label=Crbug Short URL
uda.crbugnumber.type=numeric
uda.crbugnumber.label=Crbug Number
uda.githubtitle.type=string
uda.githubtitle.label=Github Title
uda.githubbody.type=string
uda.githubbody.label=Github Body
uda.githubcreatedon.type=date
uda.githubcreatedon.label=Github Created
uda.githubupdatedat.type=date
uda.githubupdatedat.label=Github Updated
uda.githubclosedon.type=date
uda.githubclosedon.label=GitHub Closed
uda.githubmilestone.type=string
uda.githubmilestone.label=Github Milestone
uda.githubrepo.type=string
uda.githubrepo.label=Github Repo Slug
uda.githuburl.type=string
uda.githuburl.label=Github URL
uda.githubtype.type=string
uda.githubtype.label=Github Type
uda.githubnumber.type=numeric
uda.githubnumber.label=Github Issue/PR #
uda.githubuser.type=string
uda.githubuser.label=Github User
uda.githubnamespace.type=string
uda.githubnamespace.label=Github Namespace
uda.githubstate.type=string
uda.githubstate.label=GitHub State
uda.gerritsummary.type=string
uda.gerritsummary.label=Gerrit Summary
uda.gerriturl.type=string
uda.gerriturl.label=Gerrit URL
uda.gerritid.type=numeric
uda.gerritid.label=Gerrit Change ID
uda.gerritbranch.type=string
uda.gerritbranch.label=Gerrit Branch
uda.gerrittopic.type=string
uda.gerrittopic.label=Gerrit Topic
uda.adoactivity.type=string
uda.adoactivity.label= Azure Devops Activity
#uda.adodescription.type=string
#uda.adodescription.label=Azure Devops Description
uda.adoid.type=numeric
uda.adoid.label=Azure Devops ID number
uda.adonamespace.type=string
uda.adonamespace.label=Azure Devops Namespace
uda.adoparent.type=string
uda.adoparent.label=Azure Devops Parent Work Item Name
uda.adopriority.type=numeric
uda.adopriority.label=Azure Devops Priority
uda.adoremainingwork.type=numeric
uda.adoremainingwork.label=Azure Devops Amount of Remaining Work
uda.adostate.type=string
uda.adostate.label=Azure Devops Work Item State
uda.adotitle.type=string
uda.adotitle.label=Azure Devops Title
uda.adotype.type=string
uda.adotype.label=Azure Devops Work Item Type
uda.adourl.type=string
uda.adourl.label=Azure Devops URL
# uda.gmaillabels.type=string
# uda.gmaillabels.label=GMail labels
# uda.gmaillastmessageid.type=string
# uda.gmaillastmessageid.label=Last RFC2822 Message-ID
# uda.gmaillastsender.type=string
# uda.gmaillastsender.label=GMail last sender name
# uda.gmaillastsenderaddr.type=string
# uda.gmaillastsenderaddr.label=GMail last sender address
# uda.gmailsnippet.type=string
# uda.gmailsnippet.label=GMail snippet
# uda.gmailsubject.type=string
# uda.gmailsubject.label=GMail Subject
# uda.gmailthreadid.type=string
# uda.gmailthreadid.label=GMail Thread Id
# uda.gmailurl.type=string
# uda.gmailurl.label=GMail URL
#! /bin/bash
# put the search string into the search file
search_file=".task/task-pane-search"
refresh_file=".task/task-pane-refresh"
if [ $# -gt 0 ]; then
echo "${@}" > $search_file
fi
# start the task pane up if it doesn't exist
task_pane=$(readlink -f ~/bin/task-pane)
matching_pane=$(tmux list-panes -F "#{pane_id} #{pane_start_command}" | grep "^%[0-9]* $task_pane$" | cut -d' ' -f 1)
if [ -z "$matching_pane" ]; then
tmux split-window -p 40 -vbd ~/bin/task-pane
else
date > $refresh_file
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment