# Reported issues for ttctl

Pod holds 22 of 219 problems reported by people outside the maintainer team. Issues filed by the project's own owners, members and collaborators are excluded entirely — a maintainer's release checklist is not a warning to a prospective user.

Back to [ttctl](/mcp/ttctl).

## Most discussed

### (feat) applications interviews notes update: UpdateInterviewTalentNotes mutation wrapper (CLI + MCP)

> [!WARNING]
> **BLOCKED on vendor (2026-06-12)** — the talent-notes write surface is broken or unexposed in the Toptal UI in every checked state (scheduled interview 2026-06-03; during the live 2026-06-05 interview), and the live-probed API write model is append-only with **undeletable** guide-section notes — unsafe to ship from inference. **Do NOT test-write against a real account.** Implementation preserved on branch `feat/441-interview-notes-update` (HEAD d115661; closed draft PR #676). Unbl

[Read the thread](https://github.com/alexey-pelykh/ttctl/issues/441) · 2026-05-20 · open · 6 comments

### surveys submit: OPEN_TEXT question with a sentinel option rejects free text

`ttctl_surveys_submit` (and the `surveys submit` CLI path) cannot answer free-text questions on `ENGAGEMENT_ENDED` surveys. Every `OPEN_TEXT` question on that survey kind is returned by the API carrying a single sentinel option in `answers[]`, and `buildSurveyAnswers` dispatches question type on `answers.length` rather than on `inputType` — so the question takes the multiple-choice path and real prose is validated against a one-item enum.

## Environment

| Detail | Value |
|--------|-------|
| 

[Read the thread](https://github.com/alexey-pelykh/ttctl/issues/877) · 2026-08-11 · closed · 4 comments

### (wire-broke) external_update — twitter field rejected, whole call fails

### TTCtl version

v0.1.0-rc.7

### Exact command that failed

MCP tool call via Claude Code:

```jsonc
{
  "tool": "ttctl_profile_external_update",
  "args": {
    "linkedin": "https://www.linkedin.com/in/<handle>",
    "github": "https://github.com/<handle>",
    "website": "https://<personal-domain>",
    "twitter": "https://x.com/<handle>"
  }
}
```

### Error output (stderr + stdout)

```
Error: ttctl_profile_external_update failed (GRAPHQL_ERROR): External profile update failed: Variable $

[Read the thread](https://github.com/alexey-pelykh/ttctl/issues/526) · 2026-05-23 · closed · 3 comments

### (feat) profile.employment.add: custom (non-catalog) workplace via employerId:null

# Feature: support custom (non-catalog) workplace on `employment.add`

> **Updated 2026-05-19**: original framing ("create a new employer in Toptal's catalog") was based on an incorrect premise — there is no `CreateEmployer` mutation. The real mechanism is the existing `CreateEmployment` with `employerId: null`. Decision: **Option C** (see [decision comment](#) and § Decision below). Original Options A/B and their ACs are superseded.

## Use case

I need to add an employment entry to my Toptal p

[Read the thread](https://github.com/alexey-pelykh/ttctl/issues/401) · 2026-05-19 · closed · 3 comments

### (wire-broke) basic_update / employment_update / employment_add send null for server-required non-null fields

# Bug: write mutations send `null` for server-required non-null fields

## Environment

- **TTCtl version**: `v0.1.0-rc.3` (2026-05-19) — the release that closed #379
- **Invocation**: MCP server (via Claude Code MCP host); not CLI
- **Node.js**: v26.0.0
- **OS**: macOS (Darwin 25.3.0)
- **Toptal surface**: `talent-profile` (per dry-run preview output)
- **`auth status`**: valid (read tools work — `basic_show`, `employment_show`, `industries_list`, `skills_list` all return data)

This is a **wir

[Read the thread](https://github.com/alexey-pelykh/ttctl/issues/392) · 2026-05-19 · closed · 3 comments

### (feat) MCP list tools: add pagination to jobs/applications/viewed surfaces

## Problem / Motivation

The MCP list tools — `ttctl_jobs_list`, `ttctl_applications_list`, and `ttctl_jobs_viewed` — return only the first page of results (≤20 items) with no exposed pagination parameter. The Toptal portal paginates through the same data source, so the wire likely supports pagination — the MCP wrapper just doesn't surface it.

In practice this means callers cannot iterate beyond the first 20 items:

- A user with 37+ eligible jobs sees only ~20 via `ttctl_jobs_list`.
- A user w

[Read the thread](https://github.com/alexey-pelykh/ttctl/issues/369) · 2026-05-18 · closed · 3 comments

### MVP MCP tools: profile_show + profile_update

## Summary

Register the first two MCP tools on the (currently empty) MCP server: `profile_show` (read) and `profile_update` (write). Both call the same code paths as their CLI counterparts.

## Context

Per `CLAUDE.md` § MCP Integration: `ttctl mcp` starts an MCP server on stdio. Currently the server is built but registers no tools. This issue is the MVP MCP surface — two tools that mirror the read/write profile vertical.

The tools must be demonstrable from a Claude Desktop config — once regis

[Read the thread](https://github.com/alexey-pelykh/ttctl/issues/10) · 2026-05-05 · closed · 3 comments

### Expose timesheet approval status, not just submission state

## Problem / Motivation

TTCtl's timesheet tools surface only talent-side submission state. There's no way to answer a routine question: have any of my submitted timesheets been approved, or are they still waiting on someone else?

Across `ttctl_timesheet_list`, `ttctl_timesheet_pending_list`, and `ttctl_timesheet_show`, the only status-bearing fields returned are:

- `timesheetSubmitted` — whether *I* submitted it
- `timesheetOverdue`

No `approved`, `approvalStatus`, `approvedAt`, `approver`, 

[Read the thread](https://github.com/alexey-pelykh/ttctl/issues/849) · 2026-07-10 · closed · 2 comments

## Most recent

### (chore) wire-routing manifest gate: enforce docs/wire-validation-routing.md against invoked ops

Follow-up from #883 (PR #883 body, § *Known gap, unowned — flagged for a maintainer call*). The second of the two ungated cross-domain doc surfaces. The maintainer call has been taken: build the mechanical check.

## The gap

CLAUDE.md § Track 1 vs Track 2 disposition states the rule outright: *"The manifest must be updated in the same PR as any new op invocation."* The manifest is [`docs/wire-validation-routing.md`](../blob/main/docs/wire-validation-routing.md), the authoritative per-op `T1` / 

[Read the thread](https://github.com/alexey-pelykh/ttctl/issues/887) · 2026-08-12 · closed · 1 comment

### (chore) mcp tool-catalog gate: enforce packages/mcp/README.md against the EXPECTED_TOOLS roster

Follow-up from #883 (PR #883 body, § *Known gap, unowned — flagged for a maintainer call*), which widened `schema-contract-disposition` but deliberately left `doc-surface` narrow, on the grounds that *"an attestation is not detection"* — and named two cross-domain doc surfaces that are genuinely ungated. This is the first of the two. The maintainer call has been taken: build the mechanical check.

## The gap

`packages/mcp/README.md` carries a tool catalog — a stated total plus a per-domain brea

[Read the thread](https://github.com/alexey-pelykh/ttctl/issues/886) · 2026-08-12 · closed · 1 comment

### (feat) MCP-layer sliding-window rate limit for bulk job-signal mutations (anti-automation)

## Problem / Motivation

[ADR-009 § "What We're NOT Solving"](hq/engineering/adr/ADR-009-per-domain-consent-vocabulary.md)
explicitly carved this out as a **separate work item** to be created when #475
is picked up:

> **Anti-automation enforcement for #475** — the ≤20-id batch bound is
> README-discipline; the council's security panelist flagged that an agent can
> loop 50×20 (1000-id mass-view, schema-compliant). This needs an MCP-layer
> sliding-window rate limit, tracked as a **separate work

[Read the thread](https://github.com/alexey-pelykh/ttctl/issues/818) · 2026-06-16 · open · 0 comments

### (chore) disposition transitive npm-audit advisories (hono/ip-address/qs via MCP SDK — unreachable on stdio)

## Problem / Motivation

`pnpm audit --prod` reports **9 advisories** (1 low, 8 moderate) ahead of the v0.1.0 stable cut. A security-aware Toptal engineer runs `npm audit` right after `npm i -g ttctl` — for a credential-handling, Cloudflare-TLS-impersonating tool, "9 vulnerabilities" with no explanation on day one is a first-contact trust ding.

All 9 are **transitive through `@modelcontextprotocol/sdk`** and sit in its optional HTTP/SSE transport stack:

| Severity | Package | Advisory (abbrev.

[Read the thread](https://github.com/alexey-pelykh/ttctl/issues/770) · 2026-06-13 · closed · 1 comment

### (docs) packages/mcp/README.md: recount tool catalog for v0.1.0 stable (88 → actual, add surveys)

## Problem / Motivation

`packages/mcp/README.md:34` states the MCP server "registers **88 tools** (at time of writing)" and gives a per-domain breakdown. Both are stale on the published surface — and `packages/mcp/README.md` ships inside the `@ttctl/mcp` npm tarball, so this is the permanent first-impression catalog a v0.1.0-stable user reads.

Evidence (2026-06-13, against `main`):

- **Count undercount (~47%)**: README says 88; two independent counts of `"ttctl_*"` literals put the real total

[Read the thread](https://github.com/alexey-pelykh/ttctl/issues/769) · 2026-06-13 · closed · 1 comment

### (feat) readme-verbs gate: check ttctl_* MCP tool-name claims against MCP tool registrations

Follow-up to #762 (PR #764), from the post-submit review's frame-level finding.

The README "What It Does" section carries MCP tool-name claims (e.g. the Jobs bullet ships `ttctl_jobs_apply_similar_answers`). `scripts/check-readme-verbs.ts` reports these as unchecked by design — its comparison universe is the CLI command tree only. But MCP tool names are mechanically checkable in principle against the tool registrations under `packages/mcp/src/tools/**`, so the #751 drift class has an MCP-parity

[Read the thread](https://github.com/alexey-pelykh/ttctl/issues/765) · 2026-06-12 · closed · 2 comments

### (wire-broke) surveys submit: INTERVIEW_ENDED occurred checkbox unanswerable

### TTCtl version

`0.1.0-rc.13` (MCP server launched as `npx ttctl@next mcp`)

### Exact command that failed

MCP tool `ttctl_surveys_submit` (host: Claude Code), against a pending `INTERVIEW_ENDED` survey:

```json
{
  "surveyId": "<survey-id>",
  "answers": [
    { "questionId": "VjEtSW50ZXJ2aWV3RW5kZWRTdXJ2ZXlRdWVzdGlvbi0w", "value": "5" },
    { "questionId": "VjEtSW50ZXJ2aWV3RW5kZWRTdXJ2ZXlRdWVzdGlvbi0z", "value": "unsure" },
    { "questionId": "VjEtSW50ZXJ2aWV3RW5kZWRTdXJ2ZXlRdWVzdGlvbi0

[Read the thread](https://github.com/alexey-pelykh/ttctl/issues/754) · 2026-06-12 · closed · 2 comments

### (fix) README: drop unshipped timesheet update verb until #458 lands

## Problem / Motivation

`README.md:38` reads "**Timesheets** — list, view, submit, and update timesheet billing cycles", but `UpdateTimesheet` has no invocation in core and `packages/cli/src/commands/timesheet/` registers only list / show / submit / pending. The README ships in the npm package — users are invited to invoke a verb that does not exist.

How it happened: docs issue #431 ("append update verb") was closed completed on 2026-05-22 via e8b25dc while its prerequisite #458 (the `timeshee

[Read the thread](https://github.com/alexey-pelykh/ttctl/issues/751) · 2026-06-12 · closed · 1 comment

### (docs) security: classify surveys_* MCP tools in MCP-leakage threat model

Discovered during #704 (surfacing the shipped surveys domain in the storefront READMEs) — out of that issue's scope, deferred here.

## Gap

The `surveys` domain shipped in rc.13 (#671–#674) with three MCP tools:

- `ttctl_surveys_list` — read (pending surveys)
- `ttctl_surveys_submit` — destructive write (IRREVERSIBLE, consent-gated)
- `ttctl_surveys_feedback` — destructive write (IRREVERSIBLE, consent-gated)

`docs/security/mcp-leakage-threat-model.md` has **zero** `survey` mentions (`grep -i 

[Read the thread](https://github.com/alexey-pelykh/ttctl/issues/725) · 2026-06-04 · closed · 1 comment

### (fix) MCP upload sandbox: resolve symlinks to close traversal/exfil bypass

Surfaced by the 0.1.0 release-readiness audit (2026-06-03). **Defense-in-depth bypass of a documented control; localized fix.**

## Problem
The MCP file-upload path-prefix sandbox does not resolve symlinks. `validateSandbox` (`packages/mcp/src/tools/file-upload.ts:228`) uses `path.resolve(filePath)` (logical `..` normalization only) and compares against `~/Documents | ~/Downloads | ~/Desktop`; there is no `realpath` / `lstat` / `readlink` anywhere in the upload path. The service then reads the t

[Read the thread](https://github.com/alexey-pelykh/ttctl/issues/707) · 2026-06-03 · closed · 1 comment

### (docs) README: surface shipped surveys domain; drop coming note

Surfaced by the 0.1.0 release-readiness audit (2026-06-03). **Storefront accuracy — fix before tagging.**

## Problem
The `surveys` domain shipped (rc.13; #671-#674: `ttctl surveys list/submit/feedback`, `core` `export * as surveys` at `packages/core/src/index.ts:89`, MCP `ttctl_surveys_*`) but the storefront still hides it:
- `README.md:34-42` "What It Does" — no surveys bullet.
- `README.md:64` — still reads "the `surveys` commands will appear under What It Does as they ship" (future framing).

[Read the thread](https://github.com/alexey-pelykh/ttctl/issues/704) · 2026-06-03 · closed · 1 comment

### (docs) README: align Node minimum to >=22.19.0 across package READMEs

Surfaced by the 0.1.0 release-readiness audit (2026-06-03). **Storefront accuracy — fix before tagging.**

## Problem
Four package READMEs state "Requires Node.js ≥ 24" but the published `engines.node` is `>=22.19.0`:
- `packages/{cli,core,mcp,ttctl}/README.md:22` → "Node.js ≥ 24"
- `packages/{cli,core,mcp,ttctl}/package.json` → `"engines": { "node": ">=22.19.0" }`
- Root `README.md:70` + root `package.json:9` → `>=22.19.0` (correct). CLAUDE.md frames Node 24 only as the release-action default L

[Read the thread](https://github.com/alexey-pelykh/ttctl/issues/703) · 2026-06-03 · closed · 1 comment

### (feat) applications interview show: inline job.title (Approach B of #694)

## Problem

Split from #694 (**Approach B**). `applications interview show` carries `job` only as `{ id, activityItemId }` — no human-readable **title**. A user can't tell *which job* an interview is for without a second command. #694 Approach A added a discovery footer pointing at `applications show <activityItemId>`; Approach B surfaces the title **inline** on the interview detail.

## Scope

Add `job.title` to the `Interview` op selection + projection + renderer:

- `packages/core/src/service

[Read the thread](https://github.com/alexey-pelykh/ttctl/issues/696) · 2026-06-03 · closed · 1 comment

### (feat) extend CLI/MCP output on interview + availability-request ops for better UX

## Problem

The interview + availability-request read ops (`applications interview show`, `applications interview notes show`, `applications interview guide show`, `applications availability-request show`) intentionally trim heavy job-context sub-trees from the wire — now documented as **BY-DESIGN wire trims** (#685). The trimmed context is reachable via a sibling command (`applications show <activityId>`), but:

1. **The end user isn't told.** Only the JSDoc (contributor-facing) names the sibli

[Read the thread](https://github.com/alexey-pelykh/ttctl/issues/694) · 2026-06-03 · closed · 1 comment

The remaining reports are on [the project's issue tracker](https://github.com/alexey-pelykh/ttctl/issues).
