Prompt examples
There is no special syntax to learn: every prompt below is just plain language, the same way you would describe the task to a colleague. What changes from one to the next is how much detail you give up front versus how much you leave for tCode to figure out by reading the code itself.
A short, direct prompt works well when the task is narrow and you already know roughly where the problem lives: "fix the off-by-one error in the pagination logic in api/views.py" gives tCode a specific file to start from and a specific kind of bug to look for, so it can usually get straight to reading that file rather than spending a turn figuring out where to look.
A broader, more exploratory prompt works well when you do not know exactly where something lives, or the problem is defined by its symptom rather than its cause: "users are reporting that exporting a large report times out, find out why and fix it" asks tCode to do the investigation itself, tracing the export path, checking for anything that would explain a timeout specifically at larger sizes, before proposing anything.
A prompt that names an outcome rather than a mechanism leaves the most room for tCode to choose its own approach: "make the CSV export handle files over a million rows without running out of memory" does not say how, streaming, chunking, a different library, only what the result needs to satisfy, which is often the right level of detail when you care about the constraint more than the implementation.
Prompts that combine a task with a connector reference are common once a project has real history attached to it in other tools: /linear followed by a ticket search, then "implement this", hands tCode the actual acceptance criteria from the issue instead of you retyping them. The same pattern works for a bug report pulled from /gmail or a design note pulled from /notion.
See Outputs for what tCode's response actually looks like for prompts like these, Run times for what affects how long a turn takes, and Thinking process for how the effort level changes how much reasoning happens before tCode acts.
A prompt does not need to be grammatically tidy or carefully structured to work well. Pasting a raw error message with a one-line "fix this" in front of it works exactly as well as writing the same request out as a full sentence, since what tCode is actually reading is the content of the error and the surrounding code, not the phrasing of your message. The effort is better spent making sure the underlying information, the actual error, the actual file, the actual constraint, is present, rather than polishing how it is worded.
Multi-part requests in a single prompt work fine and are common: "fix the bug, add a test for it, and update the changelog" is one message that produces one turn covering all three, in that order, rather than requiring three separate round trips. The chat loop (see The chat loop) does not treat a compound request any differently from a simple one, it just means more tool calls happen before the turn is done.
It is fine, and often useful, to be explicit about constraints a prompt would not otherwise convey: "do not touch the public API, only the internal implementation" or "keep this compatible with Python 3.9" changes what tCode considers an acceptable solution before it starts, rather than something you have to catch and correct after seeing a first attempt that violated a constraint you had not actually stated.
A prompt referencing a specific file or function by name is not required, but it does narrow where tCode starts looking, and is worth including whenever you already know it. "Fix the retry logic" versus "fix the retry logic in services/http_client.py" both work, the second simply skips the step of finding that file first, which matters more on a larger codebase than a small one.
Ten worked examples
The ten prompts below cover most of the shapes a real request takes: narrow fixes, broad investigations, refactors, tests, reviews, and a couple that lean on connectors or plan mode. Each one shows the prompt as typed, then a representative transcript in the exact format tCode actually prints, tool headers, permission prompts, the closing summary. These transcripts are illustrative rather than a literal recording of one specific run, since the exact files, line counts, and wording a real session produces depend on your own codebase, but the shape, which tools get called, in what order, what the permission prompt and the summary look like, is accurate to how tCode actually behaves.
1. A narrow, well-scoped fix
> fix the off-by-one error in the pagination logic in api/views.py
● Read(api/views.py) ● Edit(api/views.py) L +1 -1 tCode wants permission → edit_file File: api/views.py Allow? [y/N/always]: y Found it: the page slice used <= instead of < against the page size, which included one extra row on every page after the first. Fixed. Read and edited views.py.
A narrow prompt like this usually resolves in the fewest possible steps: one read to confirm exactly where the bug is, one edit, done. There is little for tCode to investigate beyond the single function named.
2. A symptom-first investigation
> users are reporting that exporting a large report times out, find out why and fix it
> ● Grep("def export_report") > ● Read(reports/export.py) > ● Grep("REQUEST_TIMEOUT") > ● Read(reports/serializers.py) > ● Edit(reports/export.py) L +14 -3 tCode wants permission → edit_file File: reports/export.py Allow? [y/N/always]: y The export builds the entire report in memory before writing it out, which is fine for a small report but scales linearly with row count. Switched it to stream rows as they are generated instead of buffering the whole thing first. Read and edited export.py, read serializers.py, searched for "def export_report", "REQUEST_TIMEOUT".
This prompt gives a symptom, not a cause, so the transcript is dominated by investigation: searching for the relevant code, reading the pieces that touch report generation, before a single edit is even proposed.
3. A rename across several files
> rename the User.legacy_id field to User.external_id everywhere it's used
> ● Grep("legacy_id") > ● Edit(models/user.py) > ● Edit(serializers/user.py) > ● Edit(migrations/0042_rename_legacy_id.py) > ● Edit(views/account.py) tCode wants permission → edit_file File: models/user.py Allow? [y/N/always]: always Renamed legacy_id to external_id across the model, serializer, one view that referenced it directly, and added a migration. Edited 4 files, searched for "legacy_id".
Answering always on the first edit here is a common pattern for a rename you already know is mechanical: it avoids four separate identical approvals for what is genuinely the same kind of change repeated across files.
4. Writing tests for uncovered code
> write tests for the discount calculation in cart/pricing.py, it has none right now
> ● Read(cart/pricing.py) > ● Glob("tests/cart/*") > ● Write(tests/cart/test_pricing.py) tCode wants permission → write_file File: tests/cart/test_pricing.py (1,840 characters) Allow? [y/N/always]: y > ● Bash(pytest tests/cart/test_pricing.py) ↳ exit 0 Added coverage for the percentage discount, the flat-amount discount, stacking both, and the zero-quantity edge case. All four pass. Read pricing.py, edited test_pricing.py, searched for "tests/cart/*", ran 1 command.
Note the Glob call before writing: tCode checks the existing test folder's structure and naming convention first, so the new file lands where a human would have put it rather than in an arbitrary location.
5. A pre-review pass before opening a pull request
> review my current diff against main for anything a reviewer would flag before I open a PR
> ● Bash(git diff main...HEAD) ↳ exit 0 > ● Read(billing/invoices.py) > ● Grep("def test_invoice") Two things worth a look before opening this: the new refund path doesn't have a test yet, the existing suite only covers charges. And `calculate_late_fee` silently returns 0 for a null due_date instead of raising, which looks like it could hide a real data problem rather than handle it. Read invoices.py, searched for "def test_invoice", ran 1 command.
A review prompt like this is read-only end to end unless you ask tCode to actually fix what it found, which is why nothing here triggers a permission prompt: it is reporting observations, not proposing edits yet.
6. Investigating a performance regression
> the /search endpoint got noticeably slower after yesterday's deploy, find out why
> ● Bash(git log --oneline -10 -- search/) ↳ exit 0 > ● Read(search/query.py) > ● Bash(git show a3f21c9 -- search/query.py) ↳ exit 0 Yesterday's commit added a relevance re-ranking step that re-fetches each result's full document to score it, instead of using the fields already in the search index. That's an extra database round trip per result. Want me to switch it to score from the indexed fields instead? Read query.py, ran 2 commands.
tCode uses git itself through bash to look at recent history, which is often the fastest way to find what actually changed rather than reading the whole module cold. Here it stops to ask before proposing a fix, since more than one approach could reasonably address the same root cause.
7. Migrating away from a deprecated library call
> the requests library we use for the webhook client is calling a deprecated method, update it to the current API
> ● Grep("requests\.") > ● Read(integrations/webhook_client.py) > ● Search("requests library Session.request deprecated replacement") > ● Edit(integrations/webhook_client.py) L +6 -4 tCode wants permission → edit_file File: integrations/webhook_client.py Allow? [y/N/always]: y Switched from the deprecated call to a Session object with the current retry configuration API. Behavior is unchanged, just no longer relying on the deprecated path. Read and edited webhook_client.py, searched for "requests\.", searched the web for "requests library Session.request deprecated replacement".
A web search shows up here because confirming the current recommended replacement for a deprecated API is exactly the kind of thing worth checking against current documentation rather than guessing from training alone.
8. Adding a small feature end to end
> add a "resend confirmation email" button to the account settings page, reusing the existing email sending code
> ● Grep("send_confirmation_email") > ● Read(accounts/emails.py) > ● Read(templates/account_settings.html) > ● Edit(accounts/views.py) > ● Edit(templates/account_settings.html) > ● Edit(accounts/urls.py) tCode wants permission → edit_file File: accounts/views.py Allow? [y/N/always]: y Added a route and view that calls the existing send_confirmation_email function, and a button on the settings page that posts to it. Read and edited account_settings.html, read emails.py, edited urls.py and views.py, searched for "send_confirmation_email".
The two initial reads are what keep this from duplicating logic: tCode finds the existing email-sending function first and wires the new button to call it, rather than writing a new, parallel way to send the same email.
9. Implementing straight from a Linear ticket
> /linear timeout on export (dropdown shows matching issues live) > Tab to pick "ENG-482: Large CSV exports time out" > implement this
> ● Read(reports/export.py) > ● Edit(reports/export.py) L +14 -3 > ● Bash(pytest tests/reports/) ↳ exit 0 Implemented the streaming export described in ENG-482's acceptance criteria. Existing report tests still pass. Read and edited export.py, ran 1 command.
The ticket's own description and acceptance criteria travel with the reference, which is why "implement this" alone is enough, tCode is not guessing at what ENG-482 wants, it read the actual ticket.
10. Planning a broader refactor before touching anything
> /plan > split the monolithic OrderProcessor class into separate validation, pricing, and fulfillment steps
> ● Read(orders/processor.py) > ● Grep("OrderProcessor(") > ● Read(tests/orders/test_processor.py) I've read through OrderProcessor and how it's called. Here's the plan, run /exit-plan to see it in full and decide whether to proceed. Read 2 files, searched for "OrderProcessor(".
> /exit-plan Plan: extract OrderValidator, OrderPricer, and OrderFulfiller as separate classes, each covering one section of the current OrderProcessor.run() method, then have OrderProcessor call all three in sequence so the seven existing call sites don't need to change. Existing tests should keep passing unchanged since the public interface doesn't move. > looks right, go ahead > ● Write(orders/validator.py) > ● Write(orders/pricer.py) > ● Write(orders/fulfiller.py) > ● Edit(orders/processor.py) > ● Bash(pytest tests/orders/) ↳ exit 0 Edited 4 files, ran 1 command.
Notice the plan itself is built from real reads, the same call-site search a normal turn would do, so approving it afterward is approving something already grounded in the actual code, not a guess made without looking first. /exit-plan itself is a local command, not a new turn, it simply shows the plan that was already assembled; the actual writes and edits only happen once you reply telling it to proceed.