Investigated and written by Claude Code on Dominique Jost's behalf, submitted from his account. Not manually authored by Dominique. (Same convention as #23.)
Summary
tdc channel threads fails with READ_ONLY when the CLI is authenticated in read-only mode, even though listing threads is a read operation and the token carries comms:channels:read and comms:content:read.
It is the only read subcommand that behaves this way. Two siblings in the same command group, channel list and channel members list, both succeed on the same token.
Version and environment
tdc 2.0.2 (@doist/comms-cli)
- macOS (darwin 25.6.0), node v25.2.1
Auth state
$ tdc auth status
✓ Authenticated
Email: dominique@doist.com
Mode: read-only (scope: comms:channels:read comms:content:read comms:messages:read user:read workspaces:read)
Reproduction
$ tdc channel threads id:CcZXsoctdNfxcA5cdKUSB --json
{"error":{"code":"READ_ONLY","message":"This CLI is authenticated in read-only mode. Re-run `tdc auth login` without --read-only to enable write operations.","hints":["Re-run: tdc auth login (without --read-only)"]}}
Fails identically with:
- a bare channel ref (
tdc channel threads CcZXsoctdNfxcA5cdKUSB --json)
- an explicit workspace positional (
tdc channel threads CcZXsoctdNfxcA5cdKUSB 69 --json)
--limit 30, --since, and with no flags at all
Expected
A list of threads in the channel. comms:channels:read or comms:content:read should cover it.
Only this one subcommand is affected
Same token, same session:
| Command |
Result |
tdc channel threads id:<id> --json |
READ_ONLY |
tdc channel list 69 --json |
OK (same command group) |
tdc channel members list id:<id> --json |
OK (same command group) |
tdc thread view <id> --json |
OK |
tdc inbox --json |
OK |
tdc conversation list 69 --json |
OK |
tdc users 69 --json |
OK |
tdc user --json |
OK |
tdc workspaces --json |
OK |
So this is not the channel group being classified as writes wholesale. Its siblings are fine.
Where the gate might live
The blocked call consistently takes longer than a successful one:
channel threads (blocked): 1.35s, 1.43s
channel list (succeeds, hits the network): 1.05s
Because the rejection is slower than a successful round trip, it does not look like a local pre-flight check. That points toward the API rejecting the request and the CLI mapping the rejection onto READ_ONLY, rather than a client-side command classification. Not conclusive, since it could also be attempting a token refresh before a local gate, but the scope mapping on the threads-list endpoint seems like the first place to look.
Possibly a regression
In #23 (2026-06-15, tdc 1.5.0), tdc channel threads --json reached the API and failed on a Zod schema validation error for pinned. So at 1.5.0 the command was getting past auth and into response parsing. The READ_ONLY behavior appears to have arrived after that.
Caveat: I do not know whether the token in #23 was read-only, so this establishes that the command reached the API at 1.5.0, not conclusively that read-only tokens were accepted then. The v2.0.0 release notes only mention the node >=24 requirement (#44), so the gate does not appear to have been introduced there.
Impact
There is currently no read-only way to enumerate the threads in a channel from the CLI:
tdc inbox covers inbox threads only, not a given channel's threads.
- The Comms MCP
search-content is search rather than enumeration, so it cannot reliably list what is in a channel. (It also returns zero results when its channelIds filter is set, even for queries that match when unfiltered, so client-side filtering is needed.)
This blocks read-only automation that needs to find threads in a channel, for example refreshing per-quarter snippets and HQ thread IDs when squads spin up new threads.
Workaround
Use the Comms MCP search-content without the channelIds filter and filter on channelId client-side. This finds threads by title that tdc search does not return, but it is search, not enumeration, so coverage is not guaranteed.
Possibly unrelated, observed in the same session
tdc search and tdc mentions intermittently return:
{"error":{"code":"INTERNAL_ERROR","message":"fetch failed"}}
Both worked earlier in the same session with the same token, so this reads as transient rather than scope-related. Noted here so it is not lost; happy to split it into its own issue if useful.
Investigated and written by Claude Code on Dominique Jost's behalf, submitted from his account. Not manually authored by Dominique. (Same convention as #23.)
Summary
tdc channel threadsfails withREAD_ONLYwhen the CLI is authenticated in read-only mode, even though listing threads is a read operation and the token carriescomms:channels:readandcomms:content:read.It is the only read subcommand that behaves this way. Two siblings in the same command group,
channel listandchannel members list, both succeed on the same token.Version and environment
tdc2.0.2 (@doist/comms-cli)Auth state
Reproduction
Fails identically with:
tdc channel threads CcZXsoctdNfxcA5cdKUSB --json)tdc channel threads CcZXsoctdNfxcA5cdKUSB 69 --json)--limit 30,--since, and with no flags at allExpected
A list of threads in the channel.
comms:channels:readorcomms:content:readshould cover it.Only this one subcommand is affected
Same token, same session:
tdc channel threads id:<id> --jsonREAD_ONLYtdc channel list 69 --jsontdc channel members list id:<id> --jsontdc thread view <id> --jsontdc inbox --jsontdc conversation list 69 --jsontdc users 69 --jsontdc user --jsontdc workspaces --jsonSo this is not the
channelgroup being classified as writes wholesale. Its siblings are fine.Where the gate might live
The blocked call consistently takes longer than a successful one:
channel threads(blocked): 1.35s, 1.43schannel list(succeeds, hits the network): 1.05sBecause the rejection is slower than a successful round trip, it does not look like a local pre-flight check. That points toward the API rejecting the request and the CLI mapping the rejection onto
READ_ONLY, rather than a client-side command classification. Not conclusive, since it could also be attempting a token refresh before a local gate, but the scope mapping on the threads-list endpoint seems like the first place to look.Possibly a regression
In #23 (2026-06-15,
tdc1.5.0),tdc channel threads --jsonreached the API and failed on a Zod schema validation error forpinned. So at 1.5.0 the command was getting past auth and into response parsing. TheREAD_ONLYbehavior appears to have arrived after that.Caveat: I do not know whether the token in #23 was read-only, so this establishes that the command reached the API at 1.5.0, not conclusively that read-only tokens were accepted then. The v2.0.0 release notes only mention the node >=24 requirement (#44), so the gate does not appear to have been introduced there.
Impact
There is currently no read-only way to enumerate the threads in a channel from the CLI:
tdc inboxcovers inbox threads only, not a given channel's threads.search-contentis search rather than enumeration, so it cannot reliably list what is in a channel. (It also returns zero results when itschannelIdsfilter is set, even for queries that match when unfiltered, so client-side filtering is needed.)This blocks read-only automation that needs to find threads in a channel, for example refreshing per-quarter snippets and HQ thread IDs when squads spin up new threads.
Workaround
Use the Comms MCP
search-contentwithout thechannelIdsfilter and filter onchannelIdclient-side. This finds threads by title thattdc searchdoes not return, but it is search, not enumeration, so coverage is not guaranteed.Possibly unrelated, observed in the same session
tdc searchandtdc mentionsintermittently return:{"error":{"code":"INTERNAL_ERROR","message":"fetch failed"}}Both worked earlier in the same session with the same token, so this reads as transient rather than scope-related. Noted here so it is not lost; happy to split it into its own issue if useful.