Skip to content

Instantly share code, notes, and snippets.

@djmitche
Last active June 7, 2024 13:08
Show Gist options
  • Save djmitche/dd7c9f257306e6b8957759c4d5265cc9 to your computer and use it in GitHub Desktop.
Save djmitche/dd7c9f257306e6b8957759c4d5265cc9 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]
shorten = true
targets = gerrit_respond, gerrit_inprogress, gerrit_review, crbugs, crbugs-started, github-assigned, github-review
annotation_comments = false
replace_tags = True
static_tags = next, today, yesterday, contrib
static_fields = project
# CLs I do not own that need my attention
[gerrit_respond]
service = gerrit
gerrit.username = $gerrit_username
gerrit.password = $gerrit_password
gerrit.base_uri = https://chromium-review.googlesource.com/
gerrit.query = is:open+-owner:self+attention:self
gerrit.add_tags = gerrit, respond
gerrit.project_template =
gerrit.description_template = https://crrev.com/c/{{gerritid}} {{gerritsummary}}
# CLs I own that need my attention
[gerrit_inprogress]
service = gerrit
gerrit.username = $gerrit_username
gerrit.password = $gerrit_password
gerrit.base_uri = https://chromium-review.googlesource.com/
gerrit.query = is:open+owner:self+attention:self
gerrit.add_tags = gerrit, inprogress
gerrit.project_template =
gerrit.description_template = https://crrev.com/c/{{gerritid}} {{gerritsummary}}
# CLs I own that are waiting for others
[gerrit_review]
service = gerrit
gerrit.username = $gerrit_username
gerrit.password = $gerrit_password
gerrit.base_uri = https://chromium-review.googlesource.com/
gerrit.query = is:open+owner:self+-attention:self+-is:wip
gerrit.add_tags = gerrit, review
gerrit.project_template =
gerrit.description_template = https://crrev.com/c/{{gerritid}} {{gerritsummary}}
# crbugs assigned to me
[crbugs]
service = crbug
crbug.base_uri = https://bugs.chromium.org/
crbug.query = owner:[email protected] Status!=Fixed Status!=Started Status!=Duplicate
crbug.project_template =
crbug.add_tags = crbug
crbug.description_template = {{crbugurl}} {{crbugsummary}}
# crbugs assigned to me
[crbugs-started]
service = crbug
crbug.base_uri = https://bugs.chromium.org/
crbug.query = owner:[email protected] Status=Started
crbug.project_template =
crbug.add_tags = inprogress, crbug
crbug.description_template = {{crbugurl}} {{crbugsummary}}
# github issues in google-related orgs
[github-assigned]
service = github
github.username = djmitche
github.login = djmitche
github.query = is:open is:issue assignee:djmitche user:google
github.include_user_issues = False
github.include_user_repos = False
github.import_labels_as_tags = False
github.default_priority = M
github.project_template =
github.description_template = {{githuburl}} {{githubtitle}}
github.token = $github_token
# github PRs in google-related orgs
[github-review]
service = github
github.username = djmitche
github.login = djmitche
github.query = is:open is:pr review-requested:djmitche user:google
github.include_user_issues = False
github.include_user_repos = False
github.import_labels_as_tags = False
github.default_priority = M
github.add_tags = respond
github.project_template =
github.description_template = {{githuburl}} {{githubtitle}}
github.token = $github_token
#! /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.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=2
urgency.user.tag.yesterday.coefficient=3
urgency.user.tag.plane.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
# END Bugwarrior UDAs
# relative priority adjustments
urgency.tags.coefficient=0
# recurring tasks' due dates aren't that important (especially before they're due)
urgency.due.coefficient=0.5
# turn off confirmations
confirmation = no
bulk = 5
recurrence.confirmation = no
# stop prompting for news
news.version=2.6.0
#! /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