tdc channel threads fails with READ_ONLY for a read-only token, while sibling read commands succeed
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 0
- Forks
- 0
- Avg merge
- 6h 35m
- Merged PRs (30d)
- 10
Description
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
tdc2.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.43schannel 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 inboxcovers inbox threads only, not a given channel's threads.- The Comms MCP
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-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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at the tdc channel threads command entry point and compare its request or authorization path with channel list and channel members list, which succeed with the same read-only token. Check the threads-list API interaction and how its rejection is mapped to READ_ONLY. Done means tdc channel threads enumerates channel threads in read-only mode while the existing sibling commands remain unaffected.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, authentication, cli
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100