Skip to content

Course · Claude Code · Daily workflow

Feed the agent input inline with ! and @

The agent can only work from the facts you give it, and the cheapest facts to give are the ones already on your screen. Say a test run in the payments service - the small TypeScript example this course uses - just failed on the duplicate-payment path. The naive move is to describe it: “The tests are red, something about duplicates.” A description is lossy: the agent guesses, re-runs what you already ran, and spends a turn recovering facts that were sitting in your terminal a second ago. This lesson is the pair of moves that hand over the real thing instead.

A line that starts with ! runs in your shell, immediately, without going through Claude. The interactive-mode reference is explicit: shell mode doesn’t require Claude to interpret or approve the command, and the command plus its output land in the conversation context. That makes it the one way to run a shell command with no permission round-trip - file edits and most shell commands the agent runs itself go through the permission system, but ! is you typing in your own shell, with the transcript watching. (The permissions rules lesson shows where ! sits among the approval rules.)

The test suite is the natural example. Rather than ask the agent to run the tests and wait for permission round-trips, you look yourself and hand it the result. The shape of a failing run looks like this - an illustrative, trimmed vitest output; your own run prints your own state:

> !npm test 2>&1 | tail -20
⎿ FAIL tests/payments.test.ts > processPayment > rejects duplicate idempotency keys
AssertionError: expected promise to be rejected with "duplicate payment",
but it was fulfilled.
Test Files 1 failed (1)
> that's the duplicate-payment path - the test expects processPayment to
reject a confirmed duplicate and it doesn't. read the test and the
processor, find why the rejection is missing, and fix it.

One behavior is worth knowing before it surprises you, because it has a version cutoff: since v2.1.186, once shell output lands in the transcript Claude responds to it as if you had sent a prompt, and that response is billed like one. Before v2.1.186, shell mode always added the output to context without a response. Under today’s default, the exchange above is one step longer than it reads: Claude answers the pasted failure the moment it lands, and your typed directive then steers that reply. If you want output recorded without commentary, set respondToBashCommands to false in settings.json (yours at ~/.claude/settings.json, or the project’s at .claude/settings.json) and the agent stays quiet until you speak.

Two smaller things the docs add that are worth banking. ! commands have their own history autocomplete: type a partial command and press Tab to complete it from previous ! commands in the current project, so the npm test … | tail incantation gets typed once. And pasting text that starts with ! into an empty prompt enters shell mode automatically. To leave it without running anything, press Escape or Backspace - or Ctrl+U - on the empty prompt.

The instinct to build: when you catch yourself narrating a fact to the agent - “the suite fails on the duplicate test”, “git status is dirty” - stop and ! it instead. A described fact is lossy; a pasted one is ground truth.

! also has a sibling prefix in the same keystroke family. Typing / expands a named command: /security-review src/auth.ts reads the file that defines it, substitutes your argument, and sends the assembled prompt to the model - the same mechanism behind every skill (a folder of instructions the agent loads when the task matches; the skills chapter covers building them). The widget traces one expansion, end to end, for an illustrative command; its other tabs show how far the same keystroke idea travels across tools.

This explainer covers five tools. Pi's prompt and skill surfaces are documented in the comparison below.

Showing the Claude Code command expansion.
you type
❯ /security-review src/auth.ts
the file
.claude/skills/security-review/SKILL.md
---
name: security-review
description: Security review of a file or diff
---

Review $ARGUMENTS for injection risks, authz gaps,
and secrets handling. Report findings by severity.
the model receives
Review src/auth.ts for injection risks, authz gaps,
and secrets handling. Report findings by severity.
  • invokeEvery user-invocable skill is a slash command - /security-review fires this SKILL.md. The legacy .claude/commands/<name>.md form still works.
  • args$ARGUMENTS is the full tail after the command name; $ARGUMENTS[N] or $N picks one argument by 0-based index, and named arguments can be declared in the arguments frontmatter list.
  • noteAdd disable-model-invocation: true and the model can never auto-trigger it - the pattern for side-effect commands like /deploy-dry-run.

The companion prefix is @. Type it anywhere in your prompt and Claude Code opens file-path autocomplete, per the interactive-mode docs: start typing and it completes against your project tree, so the reference is the real path rather than one you half-remember. An illustrative prompt from the payments service, pointing at the rate limiter’s async method - the one place in that file where a read and a write sit across an await, so two callers can interleave:

> the race is in @src/payments/rate-limiter.ts - allowAsync reads
the window, awaits, then writes. compare it with the synchronous allow()
in the same file and reshape it the same way.
⎿ Read rate-limiter.ts

Two reasons this beats typing a path inline. It is exact - no guessing between rate-limiter.ts and ratelimiter.ts, no wrong directory. And it is a deliberate signal: @ tells the agent this is in scope, read it without a “go look at” clause around it. It pairs naturally with !: shell output shows the agent the dynamic state of the world, and @ anchors it to the static files that state came from.

Those two moves cover getting text in: live output and exact files. Two input shapes remain. Some problems are not text at all, and some prompts need more than one line. That’s the next lesson.