Repository: GothenburgBitFactory/taskwarrior (2025-07-10T20:14:36Z)

Issue:
Reports, task info missing tags column after `task edit`
if you create a task, like:

```bash
task add project:tws +@work +_in +#md scheduled:now -- 'zxc'
```

then you edit this with:

```bash
task edit <num> # just add annotation or blank comment on end of the file
```

then tags column will no longer appear in reports. It seems that after editing the parameters, the ones added during creation now appear in the wrong order when I compare `task info <num>` before and after


Discussion:
This is similar to #3877, in that it's a place where Taskwarrior is relying on the deprecated (but usually present) `tags` property.

In this case, it seems that, when this property is not present:
 * A `tags` column in reports no longer shows the tags
 * `task info` no longer displays "Tags"

Note that tags aren't ordered, so there's no "wrong" order. But they should be displayed!
I can't replicate the issue with `task info` in the latest commit. I realize #3877 fixed the issue with `task edit` dropping the deprecated `tags` property, so I dropped it manually.
```
⸩ sqlite3 .task/*.sqlite3 .dump
...
CREATE TABLE tasks (uuid STRING PRIMARY KEY, data STRING);
INSERT INTO tasks VALUES('4676909d-967a-4566-ace4-1e093a1f5b84','{"tag_@work":"x","modified":"1752176934","description":"zxc","scheduled":"1752176909","entry":"1752176909","project":"twsss","tag__in":"x","tag_#md":"x","status":"pending"}');
...
```
```
⸩ t 1 info
...
Tags          #md @work _in
...
```
Looking at the code, this row in the `task info` output is handled correctly, looking at the `tag_<tagname>` properties. So it's not surprising that it's working. That hasn't changed since the 3.3.0 release, at least.

I _can_ confirm the issue with reports, though.
(oh, I misread the report -- the only bug reported in `task info` is regarding tag order, which is not defined so working as intended)



Patch:
diff --git a/.github/workflows/release-check.yaml b/.github/workflows/release-check.yaml
index 57cfa0dc7..715e5857a 100644
--- a/.github/workflows/release-check.yaml
+++ b/.github/workflows/release-check.yaml
@@ -15,8 +15,11 @@ jobs:
           path: ~/.cargo/registry
           key: ${{ runner.os }}-cargo-registry-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }}
 
+      - name: Update apt repos
+        run: sudo apt-get update -y
+
       - name: Install uuid-dev
-        run: sudo apt install uuid-dev
+        run: sudo apt-get install -y uuid-dev
 
       - name: make a release tarball and build from it
         run: |
diff --git a/src/columns/ColTags.cpp b/src/columns/ColTags.cpp
index 2d0f278c0..ceaf86187 100644
--- a/src/columns/ColTags.cpp
+++ b/src/columns/ColTags.cpp
@@ -66,28 +66,21 @@ void ColumnTags::setStyle(const std::string& value) {
 // Set the minimum and maximum widths for the value.
 void ColumnTags::measure(Task& task, unsigned int& minimum, unsigned int& maximum) {
   minimum = maximum = 0;
-  if (task.has(_name)) {
+  if (task.getTagCount() > 0) {
     if (_style == "indicator") {
       minimum = maximum = utf8_width(Context::getContext().config.get("tag.indicator"));
     } else if (_style == "count") {
       minimum = maximum = 3;
     } else if (_style == "default" || _style == "list") {
-      std::string tags = task.get(_name);
-
-      // Find the widest tag.
-      if (tags.find(',') != std::string::npos) {
-        auto all = split(tags, ',');
-        for (const auto& tag : all) {
-          auto length = utf8_width(tag);
-          if (length > minimum) minimum = length;
-        }
-
-        maximum = utf8_width(tags);
+      auto tags = task.getTags();
+
+      // Find the widest tag (minimum) and width of all tags together (maximum)
+      maximum = tags.size() - 1;
+      for (const auto& tag : tags) {
+        auto length = utf8_width(tag);
+        maximum += length;
+        if (length > minimum) minimum = length;
       }
-
-      // No need to split a single tag.
-      else
-        minimum = maximum = utf8_width(tags);
     }
   }
 }


Test patch:
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index bdc3347ee..513b46701 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -7,6 +7,9 @@ jobs:
   coverage:
     runs-on: ubuntu-latest
     steps:
+      - name: Update apt repos
+        run: sudo apt-get update -y
+
       - name: Install apt packages
         run: sudo apt-get install -y build-essential cmake git uuid-dev faketime locales python3 curl gcovr ninja-build
 
diff --git a/test/custom.test.py b/test/custom.test.py
index bf7a194ff..804b69833 100755
--- a/test/custom.test.py
+++ b/test/custom.test.py
@@ -74,6 +74,30 @@ def test_custom_alternate(self):
         self.assertIn("[44m", out)
 
 
+class TestReportTags(TestCase):
+    def setUp(self):
+        """Executed before each test in the class"""
+        self.t = Task()
+        self.t.config("report.foo.description", "DESC")
+        self.t.config("report.foo.labels", "ID,TAGS")
+        self.t.config("report.foo.columns", "id,tags")
+        self.t.config("report.foo.sort", "id+")
+
+    def test_report_with_tags(self):
+        """Verify custom report includes tags even when `tags` property is not included"""
+        # Create a task directly with TC, avoiding TaskWarrior's creation of the deprecated
+        # `tags` property.
+        uuid = self.t.make_tc_task(
+            status="pending",
+            description="task with tags",
+            tag_tag1="x",
+            tag_tag2="x",
+        )
+        code, out, err = self.t("foo")
+        self.assertIn("tag1", out)
+        self.assertIn("tag2", out)
+
+
 class TestCustomErrorHandling(TestCase):
     def setUp(self):
         self.t = Task()
diff --git a/test/info.test.py b/test/info.test.py
index 79ffe80fa..a951daa89 100755
--- a/test/info.test.py
+++ b/test/info.test.py
@@ -26,6 +26,7 @@
 ###############################################################################
 
 import sys
+import time
 import os
 import unittest
 
@@ -114,6 +115,21 @@ def test_info_display(self):
         self.assertRegex(out, r"U_ONE\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}")
         self.assertRegex(out, r"U_TWO\s+P1D")
 
+    def test_tags_without_tags_attribute(self):
+        """Verify info command shows tags, even if the `tags` property is not present"""
+        # Create a task directly with TC, avoiding TaskWarrior's creation of the deprecated
+        # `tags` property.
+        uuid = self.t.make_tc_task(
+            status="pending",
+            description="task with tags",
+            due=str(int(time.time())),
+            tag_foo="x",
+            tag_bar="x",
+        )
+        code, out, err = self.t("1 info")
+        # Tags can occur in any order
+        self.assertRegex(out, r"Tags\s+(bar|foo)\s(foo|bar)")
+
 
 class TestBug425(TestCase):
     def setUp(self):