Everything I Learned From My Claude Code Training
I recently went through a hands-on training on Claude Code and the broader Claude ecosystem. Below are my complete notes, organized by topic, with screenshots from the session included at each relevant step.
Table of Contents
-
The Claude Ecosystem
- AI-Code-Design-Cowork-Security-Chrome-Office
-
The Agentic Loop (Query Engine)
- Architecture
-
Models, Effort, and Slash Commands
- Models-Commands
-
Session Management
- Export-Fork-Resume-Compact
-
Project Configuration
- ClaudeFolder-Permissions-Init-ClaudeMd
-
Auto Mode vs. Plan Mode
- Auto-Plan
-
Commands vs. Skills vs. Agents
- Standup-Metaprompting-PR-Invocation
-
Code Review via Subagents
- Security-Performance-Coverage-Quality-Usage-Deploy-Setup-Execution-Tests-Stack-Creation-Example-Focus-OWASP-Memory-Output-Background
-
Multi-Agent Harness Concepts
- SecondPass-Comparison
-
Hooks
- Flow-PreToolUse-Types-Blocking-Slack-Sharing
-
MCP (Model Context Protocol)
- Concept-Rationale-Types
-
End-to-End Automation
- End-to-end automation of security review → issue creation → issue fix → PR creation → PR fix, all through agents.
-
Agent Teams
- Multiple agents (security, performance, test) talk to each other and take actions autonomously — filing issues, creating PRs, merging PRs, and closing issues, all automated.
-
Key Takeaways
- Loop-Config-Commands-Hooks-Sessions-Teams
1. The Claude Ecosystem
Claude Code is one tool within a larger ecosystem, not a standalone product:
- Claude AI — the conversational assistant
-
Claude Code — the agentic coding CLI (the focus of this article), which has:
- An agentic loop that loops until the task is completed
- A tool system: git, bash, read, write files
- A permission pipeline, configurable per tool (Allow / Deny / Ask)
- A context manager: compaction and pruning of context within a session
- Session persistence: work can be paused and resumed later, with context and memory maintained
- Claude Design — design tooling
- Claude Co-work — collaboration tooling
- Claude Security — security-focused tooling
- Claude Chrome plugin — browser automation
- Claude Office plugin — office document integration
In the backend, Claude Code talks to the LLM hosted by Anthropic via the /v1/messages API.
Diagram of the agentic loop: gather context, take action, verify results, repeat until done.

Tools like Cursor, Windsurf, and Perplexity act as integrators — giving access to multiple LLMs. Claude, by contrast, offers an entire vertically integrated ecosystem.
2. The Agentic Loop (Query Engine)
This is the core engine behind Claude Code. Given a request — e.g. "Create a unit test case for my function" — the loop works like this:
- Gather context — goes to the file, reads the imported files also, reads the function in the file (which needs to be mocked).
- Send context to the LLM — the LLM decides, based on the context, what unit test cases must be created after it has all the info, and tells Claude Code what actions to take (e.g. "create new test case").
- Use tools — Claude Code creates or modifies the file using its tool system.
- Verify — the agent checks whether the job is done or not (e.g. runs the test to confirm it passes).
Additional properties:
- The user can modify the context in between the loop or after it.
- The loop runs continuously until the task is not done.
- Everything depends on the LLM being used — so it's worth knowing which LLM is best suited for the task.
Agentic Coding Tools Architecture
Three-layer architecture diagram showing coding agents (Claude Code, Copilot, Codex, Cursor, etc.) between the chat interface and various LLM hosting backends.

Claude Code's runtime architecture, from terminal command through the CLI parser and QueryEngine agentic loop to tool calls and API routing.

- Claude Code always connects to the Anthropic base URL.
- It can also connect to Ollama and open-source models such as Qwen or Llama 3.2 — enabling a fully local setup. This is currently the only fully local option available.
3. Models, Effort, and Slash Commands
Models, effort levels, and sessions are the main levers for controlling how Claude Code works.
Models
Four main models, chosen based on the type of task and usage:
- Fable — for complex tasks: architecture, design, security, coding
- Opus
- Sonnet
- Haiku — for simpler tasks
-
Qwen-localis the local-model equivalent of Haiku. -
opusplan= Opus for planning + Sonnet for doing the task.
Guidance: try a smaller model at higher effort first — otherwise switch to a new/bigger model as required. Use Fable only if Opus cannot handle the task.
Terminal status line showing the active model, session name, context usage, token count, and running cost.

Screen clippings taken: 29-08-2026 02:21 and 29-08-2026 02:14
Slash commands for managing everything
-
/model— switch between different models -
/effort— decides the effort to put in: high → xhigh → max -
/statusline— shown in the Claude prompt, displays total tokens used; helps decide when to compact the session
4. Session Management
Before starting with Claude Code, it's worth understanding /sessions — session management.
- We can resume from wherever we stopped using a session ID.
- Start a new session:
claude -n "session_name" - Resume a previous session:
claude --resume,claude --continue -
/status— check session status
Practical guidance
- We can create 3 different sessions for 3 different issues, potentially working on 3 different models.
-
/export explore-current-project.md— exports the conversation to a markdown file, e.g.:
⎿ Conversation exported to: H:\Claude-Code-Projects-17-19-Aug\claude_showcase_17_aug_26\explore-current-project.md
-
/fork— forks the current session so you can go to a new forked session without modifying the original session. - If 80% of context is used, we can compact it.
-
/resume— switch between sessions. -
/compact— summarizes the session data; some data could be removed in the process. - If old context is not found in the current session, tokens would need to be spent again to recreate it — and wrong information could get compacted.
- There is a usage limit at the account level; every session uses a part of that limit.
- We should create multiple sessions — e.g. 3 different sessions for 3 different bugs. Information for the first bug may not be needed for the second bug, and sessions help Claude read old information without wasting tokens to recreate it.
Claude Code's /status panel showing version, session details, login/account info, and connected MCP servers.

5. Project Configuration
README.md
The most important file for reading all the information about a project.
- Before telling the tool what to do, plan first — create the architecture first.
- Decide on a design template.
CLAUDE.md — Rules
Used to control the behaviour of Claude:
- Which JavaScript library to use.
- Preventing Claude Code from reading sensitive files so it does not use them.
- How to give Claude permission to access your code — what it can read and what it cannot read.
.claude folder and settings
- First, create a
.claudefolder. - Create
settings.json— accessible to the whole team, pushed to the repo. - Create
settings.local.json— will not be pushed to the repo, and will overridesettings.json.
Here you can decide which model to use:
{
"model": "opusplan"
}
Permissions:
{
"permissions": {
"allow": [
"Bash(npm run *)",
"Bash(git status)",
"Bash(git diff)",
"Bash(git add *)",
"Bash(git commit *)",
"Read(**)",
"Write(src/**)",
"Write(tests/**)"
],
"ask": [
"Bash(git push:*)",
"Bash(npm install *)",
"Write(package.json)"
],
"deny": [
"Read(./.env*)",
"Read(./secrets/**)",
"Read(./**/credentials*)",
"Bash(rm -rf:*)",
"Bash(curl:*)",
"Bash(wget:*)"
]
}
}
Claude declining to read a blocked .env.example file per project settings and offering alternative ways to proceed.

/init
Initializes a CLAUDE.md file with codebase documentation after going through the project at the root level.
- Add conditions for Claude to always follow — should not go above 150 to 200 lines max; this is very specific to the root of the project.
- We can have a
CLAUDE.mdfile for every sub-folder. - If there is no
CLAUDE.mdfile, don't rely on Claude having project-specific context — useCLAUDE.mdfor every project.
Example: the actual CLAUDE.md used in this training repo
## Coding Standards
- Always use async/await, never raw Promises
- All functions must have JSDoc comments
- No console.log in production code — use the logger utility
- Every new function must have at least one unit test
- Pure validation functions use a single return statement with a composed expression
- Never use multiple early-return guard clauses in validator functions
## Repository conventions
- CommonJS (`require`/`module.exports`) throughout, not ESM.
- ESLint enforces: `eqeqeq` (always `===`), `curly`, `no-var`/`prefer-const`,
`no-return-await`, `require-await` (no `async` functions without an `await`),
unused-arg exception for `_`-prefixed names.
- `.claude/settings.json` denies reads of `.env*`, `secrets/**`, and
`credentials*` — don't try to work around this to inspect real secrets.
## What Claude Must Never Do
- Never modify .env or .env.\* files
- Never push directly to main branch
- Never remove existing tests
- Never install packages without confirming with the developer
## PR and Git Standards
- Commit messages follow Conventional Commits: feat:, fix:, docs:, test:
- PR descriptions must include: what changed, why it changed, how to test
## Tech Stack
- Runtime: Node.js 20
- Framework: Express.js
- Database: PostgreSQL with Prisma ORM
- Testing: Jest
- Language: ECMAScript 2022 for all new code only
## Architecture
Request flow: `src/index.js` wires Express with `requestLogger` middleware
globally, mounts all auth endpoints under `/api/auth` from `src/api/routes.js`,
and registers `errorHandler` last as the global error middleware.
Layering:
- `src/api/routes.js` — route handlers only; validates input shape, calls into
`authService`, maps thrown errors to HTTP status codes (e.g. `'Invalid
credentials'` → 401). It does NOT talk to `tokenHelper.js` directly.
- `src/api/middleware.js` — cross-cutting concerns: `requestLogger`,
`authenticate` (verifies Bearer token via `tokenHelper.verifyToken`, attaches
`req.user`), `validateBody` (checks required fields present), `errorHandler`
(catch-all, returns generic 500).
- `src/auth/authService.js` — business logic: `loginUser`, `refreshToken`,
`revokeToken`, token generation. Owns the in-memory `refreshTokenStore` (a
`Map`, standing in for a database — resets on every restart, not shared
across processes).
- `src/auth/tokenHelper.js` — low-level JWT primitives: `verifyToken`
(signature + expiry check, throws typed errors), `decodeToken` (no
verification, for reading claims only), `extractBearerToken`, `getTokenTTL`.
`authService.js` and `middleware.js` both depend on this; it depends on
nothing else in the app.
- `src/utils/validators.js` — pure, stateless input validators (email,
password, UUID, sanitization). No I/O.
- `src/utils/logger.js` — minimal structured JSON logger (stdout/stderr),
level-gated by `LOG_LEVEL` env var. Not a real observability stack.
There's no real user database: `routes.js` builds a `mockUserRecord` inline
for login, and `authService.getUserById` returns a hardcoded stub — expect
these to be replaced with real persistence rather than extended in place.
JWT config (`JWT_SECRET`, `JWT_EXPIRES_IN`, `REFRESH_EXPIRES_IN`) is read from
env vars in both `authService.js` and `tokenHelper.js` independently, each
with its own fallback default — keep them in sync if changing defaults.
### Request flow (login example)
`POST /api/auth/login` → `validateBody(['email','password'])` → route handler
validates format via `validators.js` → `authService.loginUser()` checks bcrypt
hash, calls `generateAccessToken` (signed JWT) + `generateRefreshToken` (uuid
stored in `refreshTokenStore`) → returns `{ accessToken, refreshToken, user }`.
Protected routes (`/logout`, `/me`) go through `authenticate` middleware,
which calls `tokenHelper.verifyToken` and attaches the decoded payload to
`req.user`.
### Error convention
Route handlers translate known domain errors to HTTP status codes inline
(e.g. `'Invalid credentials'` → 401) and pass everything else to `next(err)`,
where `errorHandler` logs it and returns a generic 500. Preserve this pattern
when adding routes — don't leak internal error messages to clients from the
global handler.
### Token model
Two-token scheme: short-lived signed JWT access token (claims: `sub`, `email`,
`role`) + opaque UUID refresh token stored server-side in `refreshTokenStore`
with `{ userId, createdAt }`. Refresh tokens are revoked by deleting the map
entry (`revokeToken`, logout).
Example: Generating validateCreditCard and Its Test Case from a Prompt
Note: creating a function like this automatically creates a test case also (via the write-tests skill — covered in section 7).
Claude adding a JSDoc-documented validateCreditCard function (Luhn checksum, 13-19 digits) to validators.js.

Claude adding Jest test cases for validateCreditCard covering valid Visa and Mastercard test numbers.

6. Auto Mode vs. Plan Mode
- Auto mode — Claude decides what to do automatically and does not ask for confirmation.
- Plan mode — we can ask Claude to create a plan only, without executing.
- Use
btwto ask a question which runs separately from the main section. - Always switch to manual mode before making changes you want to review first.
- In plan mode, Claude will ask you to go ahead with the changes first.
Claude's plan mode proposal for a new validateBirthDate validator, with scope and constraints confirmed before execution.

In auto mode, this behaviour changes — Claude proceeds independently.
7. Commands vs. Skills vs. Agents
-
/commands— executed manually, defined via a markdown file. -
Skills — run automatically for us, based on what's asked. A command can be converted into a skill. Skills go into a skills folder.
- There are a lot of third-party skills available.
- It's better to create our own skills, which use fewer tokens compared to those available externally, which tend to use more.
- Agents — specialized assistants that do a task in the background and come up with an answer. Run an agent separately in the background, or run it via a skill — a skill can also hand over a task to an agent.
Rule of thumb: never use one-liners to do your job — don't go back to the tool again and again.
Example: custom command — /standup
---
name: standup
description: Generate a daily standup update from git history and open files
disable-model-invocation: true
---
You are a senior developer preparing a daily standup update for your team.
This is a Node.js/Express authentication API project. You have been working
in this codebase today. Check git log --since="00:00" --oneline and
git diff HEAD to understand what actually changed.
Draft a standup update based only on what you find in the git history
and open files — do not invent or assume work that is not visible.
Format:
Yesterday: [completed items, specific function or file names]
Today: [in-progress items based on uncommitted changes or open TODOs]
Blockers: [failing tests, TODO/FIXME comments, incomplete functions]
Keep each section to 3 bullet points maximum.
Use specific names — file names, function names, issue numbers where visible.
Be factual and brief. No filler. Write it as if reading it aloud in 30 seconds.
Output of the /standup command summarizing yesterday's commits, today's work, and current blockers.

If Claude Code is used to create code, it should also be used to create the commit.
Metaprompting
Take help of Claude to create a prompt using the RCTFCF framework (Role, Context, Task, Constraint, Format):
"Act as a prompt expert and help me create a structured prompt using role, context, task, constraint, and output format which can be used to do a security review for a Node.js API written in the Express framework. I want to check top 10 OWASP issues."
Also relevant: chain of thought prompting, and the observation that LLMs understand prompting better in their own language (i.e. structured, explicit formats they're trained to parse).
Claude implementing validateDateOfBirth in validators.js following the ISO-date, no-future, 120-year-limit convention.

The /raise-pr command refusing to push directly to main and creating a feature branch instead.

Example: custom command — /raise-pr
---
name: raise-pr
description: Raise a GitHub pull request for the current branch targeting main
disable-model-invocation: true
---
You are a senior developer raising a pull request for a fix or feature branch.
Steps:
1. Run git branch --show-current to get the current branch name.
2. Run git log main..HEAD --oneline to list all commits on this branch.
3. Run git diff main...HEAD to inspect all changes introduced by this branch.
4. Check if the branch name or commits reference an issue number (e.g. fix/issue-204
or "closes #204"). Extract it if present.
Using what you observe, generate a PR title and body:
Title format:
type(scope): short summary under 72 characters (same as the commit message)
Body format:
## What changed
- Bullet points describing each logical change
## Why it changed
- The problem or issue this PR resolves (reference issue number if found, e.g. closes #204)
## How to test
- Step-by-step instructions to verify the fix or feature works
Then run:
gh pr create --base main --title "<title>" --body "<body>"
If the branch has no upstream yet, push it first:
git push -u origin <branch-name>
Output only the PR title and body. No explanation, no commentary, no preamble.
Be precise and factual. Every word should earn its place.
Running /raise-pr:
Permission prompt confirming the git push of the new feature branch to origin.

Confirmation that the feature branch was pushed and PR #1 was opened on GitHub.

We can see the PR is created:
GitHub pull requests tab showing the newly opened validateDateOfBirth PR.

Skills are auto-invoked
/commands are executed manually, but skills get applied automatically based on the prompt. Seeing a list of all skills:
The /skills menu listing all 9 available skills and their lock/enabled status.

Skills could also come from installed plugins.
8. Code Review via Subagents
A code review skill can delegate each dimension of the review to a dedicated subagent using the Task tool:
## 1. Security
Delegate this dimension to the `security-analyst` subagent using the Task tool.
## 2. Performance
Delegate this dimension to the `performance-analyst` subagent using the Task tool.
## 3. Test Coverage
Delegate this dimension to the `test-coverage-analyst` subagent using the Task tool.
## 4. Code Quality
Delegate this dimension to the `code-quality-analyst` subagent using the Task tool.
How to use a skill
Example prompt:
❯ look at the auth module and tell me if there are any problems
The code review skill should auto-invoke:
Claude auto-invoking the code-review skill and launching four parallel review agents against the auth module.

No explicit integration is needed — it's auto-invoked. No need to use /command explicitly.
A sample of the code review report:
Consolidated code review report table listing critical and high-severity findings across the auth module.

Detailed write-up of two critical findings: a syntax-breaking stray await and a bypassed bcrypt password check.

Example: skill — deploy
If I want to deploy, deploy.sh should be used — so we use a deploy skill:
---
name: deploy
description: >
Deploy the application to staging. Use when the user says "deploy",
"ship it", "push to staging", or "release". Runs pre-flight checks,
builds, tags, and verifies the deployment. Always confirm with the
user before executing.
disable-model-invocation: true
---
# Deploy Skill
Deployment logic lives in `scripts/deploy.sh`. That script is the
source of truth — do not re-implement its steps here.
## Before Running
Confirm with the user:
- "Ready to deploy to staging. This will tag and push. Proceed?"
- Do not proceed without explicit confirmation.
## Execution
Run: `bash scripts/deploy.sh`
Example: skill — write-tests
Similarly, we can use a write-tests skill to write a test case:
---
name: write-tests
description: >
Use this skill whenever new testable logic is added or modified — functions,
methods, classes, route handlers, or modules in src/. Triggers on: "add a
function", "create a utility", "implement this", "write a handler", "add a
route", or any task that produces new logic with inputs and outputs or
side effects. Do NOT wait to be asked — write tests as part of completing
the task. Skip this skill only for: config constants, type definitions,
pure re-exports, or framework boilerplate with no logic.
---
# Write Tests Skill
You are a senior QA engineer writing exhaustive Jest test suites for a
Node.js application.
## Stack & Conventions
- **Framework**: Jest with Supertest for HTTP integration tests
- **Test location**: `tests/` mirroring `src/` structure, `.test.js` suffix
- **Logger**: Structured JSON — never assert on `console` output
- **Auth**: `JWT_SECRET` defaults to `'dev-secret-key'` in tests — do not
assert real security guarantees against this value
- **Test data**: Define inline for simple cases; use `tests/fixtures/` for
objects reused across 3+ tests
Initiating the write-tests skill:
The write-tests skill identifying and filling a missing leap-year test case for validateDateOfBirth.

Skills are auto-invoked. LCOV coverage output:
Test suite results showing 76 passing tests with 100% coverage on validators.js.

Creating a custom agent
Skills, agents, and commands differ as follows:
- Agent — a specialized assistant that does a task in the background and comes up with an answer. Run it separately in the background, or run it via a skill — a skill can also hand a task over to an agent.
- In an agent definition, we can specify exactly what tools the agent can use.
Example of agents:
The project's custom subagent definitions listed in the .claude/agents folder.

Example: custom agent — security-analyst
---
name: security-analyst
description: >
Use this agent for security reviews: authentication bypass, injection vulnerabilities, OWASP Top 10, token handling, and input validation. Invoke with: ask the security-analyst to audit this file.
tools: Read, Bash(npm audit *), Bash(grep *), Write, Edit
model: opus
memory: project
---
# Security Analyst Agent
You are a senior application security engineer. You read code exclusively through
a security lens. You do not suggest feature improvements or code style changes.
You find vulnerabilities and you explain how to fix them.
## Your Focus Areas
- Authentication bypass and broken access control
- Injection vulnerabilities: SQL, command, path traversal
- Sensitive data exposure in logs, responses, or error messages
- Insecure token handling: weak secrets, missing expiry, improper storage
- Missing input validation and sanitisation
- Dependency vulnerabilities (flag for npm audit review)
## OWASP Categorisation
Tag every finding with its OWASP Top 10 category where applicable.
## Memory Protocol
Before starting, read `MEMORY.md` (an index of one-line links to note files) and
skim any linked notes whose description looks relevant to the file(s) you're
about to audit.
After completing your audit, write to memory only if you found something
non-obvious, recurring, or specific to this codebase that would save real time
on a future audit — not routine findings you'd already report. If so:
1. Create a new note file named for the pattern (e.g. `jwt-secret-hardcoded-in-config.md`)
with `name`, `description`, and `metadata: {type: feedback}` frontmatter, following
the structure used in existing notes.
2. Add a one-line link to it from `MEMORY.md`.
Do not rewrite or duplicate an existing note — update it in place if the same
pattern recurs with new detail.
## Output Format
For each finding:
- OWASP Category
- Severity: Critical / High / Medium / Low
- File and line number
- What an attacker could do with this vulnerability
- Exact fix with corrected code snippet
End with: total finding count by severity, and one recommended immediate action.
How does a skill hand over a task to an agent?
Claude launching the security-analyst subagent in the background to audit the authentication codebase.

The agent runs in the background
● Agent "Security audit of auth codebase" finished · 4m 42s
The security-analyst audit found 18 findings across authService.js, tokenHelper.js, routes.js, middleware.js, and validators.js. Two are critical and one of them also breaks the server outright.
Critical
-
Auth bypass —
bcrypt.compare(...)atauthService.js:32is called withoutawait, so the truthy Promise always passes the check. Every password is accepted for any login. -
Hardcoded JWT fallback secret —
'dev-secret-key'in bothauthService.js:11andtokenHelper.js:8, used silently ifJWT_SECRETis unset. Anyone can forge admin tokens.
High
- Login always builds an inline
mockUserRecordwith a constantid: 'user-001'and attacker-supplied email — every session collapses to the same identity, with a spoofable email claim. - No rate limiting on
/loginor/refresh— unlimited credential stuffing / refresh-token brute-forcing. -
REFRESH_EXPIRES_INis declared but never enforced — refresh tokens never expire.
Medium (6 findings) — notably: authService.js:80 is missing the async keyword on refreshToken, which is a SyntaxError that prevents the server from booting at all (confirmed via node --check) — meaning none of this is currently under test/CI coverage; an inverted expiry comparison in isTokenExpired that also deletes valid sessions; /logout can revoke anyone's refresh token (no ownership check); access tokens aren't invalidated on logout; PII (raw emails) logged on every auth attempt; unbounded in-memory token store (memory-exhaustion DoS).
Low (7 findings) — untrimmed email reaching downstream sinks, internal error messages leaked to clients via substring matching, missing JWT algorithm allowlist, missing security headers/CORS/body-size limits, loose validateBody treating null/0/false as "present," a low-severity uuid advisory (not reachable, since uuidv4() is called with no args), and a latent timing side-channel for user enumeration.
Good news: the newly added
validateDateOfBirth(commitde91f6f) was checked directly and is clean — correctly rejects rollover dates, future dates, ages over 120, and malformed input. It's not currently wired into any route, so it's not a live attack surface either way.
Recommended immediate fix (one commit): add the missing await on bcrypt.compare (line 32) and the missing async on refreshToken (line 80) together — the second is required just to get the app to boot, and without it nothing else can even be tested. Rotate JWT_SECRET and make it mandatory at startup right after.
9. Multi-Agent Harness Concepts
When Claude Code itself uses multiple subagents to work on a task, we can say it's using a multi-agent harness. The main thread uses the info from all the agents to create a single report.
A second pass: performance, test coverage, and code quality
Prompt: "Create agent for performance review, test coverage, and code quality — use the code review skill."
Claude clarifying review scope, then running performance, test-coverage, and code-quality agents in parallel while fixing the critical bugs.

Full output — Code Review Report (Performance / Test Coverage / Code Quality)
Context: Re-run of the performance, test-coverage, and code-quality dimensions against
src/auth/authService.js(security was already covered in the prior review). Findings are consistent with the earlier pass; a few additional specifics surfaced this time.
| # | Dimension | Finding | Severity |
|---|---|---|---|
| 1 | Code Quality / Test Coverage | Missing async on refreshToken (line 80) + await at line 92 → SyntaxError, module can't load, entire app fails to boot |
🔴 Critical |
| 2 | Code Quality / Test Coverage |
bcrypt.compare not awaited (line 32) → auth bypass, confirmed independently by ESLint's require-await flagging loginUser as having no real await |
🔴 Critical |
| 3 | Test Coverage | No test file exists for authService.js at all — every exported function has 0% coverage |
🔴 Critical |
| 4 | Test Coverage |
isTokenExpired inverted logic (line 68) — verified via failing Jest assertions against real signed JWTs |
🔴 Critical |
| 5 | Code Quality |
isTokenExpired duplicates tokenHelper.js's JWT logic instead of delegating (violates CLAUDE.md's documented layering), and is applied to opaque UUID refresh tokens where it always short-circuits via jwt.decode() === null
|
🟠 High |
| 6 | Code Quality |
REFRESH_EXPIRES_IN and createdAt are dead code — confirmed via ESLint no-unused-vars — refresh tokens never actually expire despite CLAUDE.md documenting a 7-day window |
🟠 High |
| 7 | Performance | Unbounded refreshTokenStore Map — no TTL sweep, grows with cumulative logins not concurrent sessions |
🟠 High |
| 8 | Test Coverage |
refreshToken(), revokeToken(), generateRefreshToken(), generateAccessToken() have no direct unit tests or edge-case coverage |
🟠 High |
| 9 | Performance |
bcryptjs (pure-JS, not native bcrypt) runs password comparison on the main thread, not the libuv threadpool — inflates tail latency under concurrent login bursts |
🟡 Medium |
| 10 | Code Quality | Swallowed error in isTokenExpired's catch (err) (line 69-71) — caught but never logged, inconsistent with the rest of the file's logging pattern |
🟡 Medium |
| 11 | Code Quality |
curly ESLint rule violation on line 64 (if (...) return true; without braces) |
🟡 Medium |
| 12 | Code Quality |
require-await violation on getUserById (line 153) — declared async with no await inside |
🟡 Medium |
| 13 | Test Coverage |
loginUser with no userRecord, JWT claims shape, getUserById stub contract — untested but low-risk edge cases |
🟢 Low |
| 14 | Code Quality |
revokeToken's "not found" branch logs nothing, inconsistent with other failure branches in the file |
🟢 Low |
| 15 | Code Quality | Imprecise JSDoc return shapes (@returns {object} instead of a typed shape) |
🟢 Low |
New since the last pass
- Tooling is broken at the project level:
npm run lintcurrently fails outright — the repo ships.eslintrc.json(legacy config) but has ESLint 10.9.1 installed, which requires flateslint.config.*. No flat config exists at the repo root. The code-quality agent worked around this by building an equivalent flat config in the scratchpad to get real lint signal (never touched the actual repo files), but as things stand today,npm run lintgives false confidence — it fails before it even reaches your code. This is worth fixing independent ofauthService.js. -
bcryptjsvs native bcrypt (finding 9): not raised as a distinct finding in the first pass — flagged now because it compounds with the login-bypass fix. Onceawaitis added back (finding 2), every login request will run a real CPU-bound bcrypt comparison on the main thread; under concurrent login bursts this inflates latency for unrelated concurrent requests. Not urgent, but worth knowing before assuming the fix is "free."
Summary Score: 2/10 (unchanged) — the file can't boot, has zero test coverage, and now additionally the project's lint tooling can't even verify it.
Recommended First Action: unchanged — fix the missing async on refreshToken (line 80) together with the missing await on bcrypt.compare (line 32) in one commit — nothing else here (tests, lint, the rest of the app) is reachable until the module can actually load. Separately, worth a quick fix to add eslint.config.js so npm run lint works again — that's a repo-wide gap, not specific to this file.
This is a hub and spoke model — using subagents via a skill. However, what we actually want in some cases is agents communicating with each other independently in the background to finish a task; the hub-and-spoke approach above cannot do that. Agents can instead be run using other agentic harnesses — loop, schedule, etc.
Difference between agent and agentic
Comparison diagram contrasting framework-driven "Agent" orchestration with model-native "Agentic" execution.

- Goal, loop, schedule, workflow, batch — agentic primitives available which help create subtasks (e.g. ultraplan, autofix-pr).
-
/commandsare manual. - Skills get applied automatically based on the prompt.
-
/agentruns in the background.
10. Hooks
HOOKS, PLUGINS, MCP e2e flow — from evaluating code to fixing code, full auto flow.
Lifecycle methods available in Claude: from the start to the end of a session, internal events fire inside the session, which are used as plug points for our own code.
Hook event flow
Whenever Claude reaches a lifecycle boundary — e.g. when a tool is about to run — it spawns our custom script as a subprocess and feeds it a JSON blob on stdin. The script can execute anything, then return:
- an exit code (
0or2), or - a JSON object on stdout containing a structured decision, reason, and context for Claude to read.
Hooks can be created using Python or JavaScript. They take input from Claude Code, display it on screen, and allow the tool to run.
Example: PreToolUse hook on Bash
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/bash-guard.js"
},
{
"type": "command",
"command": "node .claude/hooks/pretooluse-demo.js"
}
]
}
]
}
}
Since this hook sits directly in front of every Bash call, it's a good interception point for anything you want enforced before a shell command runs. Some concrete uses, especially relevant to a repo like this:
-
Guardrails / safety (this is what
bash-guard.jsalready seems to do) — block dangerous patterns beyond whatsettings.json's deny-list covers, e.g.rm -rfvariants,git push --force, piping tosh, etc., using regex instead of exact string matching.
All hook handler types
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "node .claude/hooks/pretooluse-demo.js" },
{ "type": "http", "url": "https://www.example.com/validate" },
{ "type": "prompt", "prompt": "Evaluate the steps before running the tool" },
{ "type": "agent", "prompt": "Run security-analyst to verify the issues" },
{ "type": "mcp_tool", "server": "some-mcp-server", "tool": "validate_edit" }
]
}
]
}
}
The bash-guard hook's log file recording every Bash command it allowed during the session.

Blocking dangerous commands
const DANGER_PATTERNS = [
/rm\s+-rf\s+\//, // recursive force-delete from root (e.g. "rm -rf /")
/sudo\s+rm/, // any sudo-elevated delete — bypasses normal permission checks
/:\(\)\{\s*:\|:&\s*\}\s*;\s*:/, // fork bomb — ":(){ :|:& };:" spawns processes until the system locks up
/dd\s+if=.*of=\/dev\/sd/, // raw disk write via dd — can overwrite an entire drive
/chmod\s+777\s+\//, // world-writable permissions on root — opens up the whole filesystem
];
Notification on Slack
{
"Notification": [
{
"matcher": "permission_prompt",
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/slack-notify.js"
}
]
}
]
}
Logs can be used to audit our work — when we did what, and how much time we spent.
Sharing configuration
What if I want to use all my settings in the .claude folder in another project? This can be done via GitHub. But how can we give it to a third party? Through plugins — see code.claude.com/docs/en/plugins for how to create one.
11. MCP (Model Context Protocol)
The concept of an MCP server
The GitHub REST API knows how to connect to GitHub on your behalf.
Diagram showing Git Bash and the GH CLI both reaching GitHub's SaaS platform through the GitHub REST API.

Either Claude directly connects via Bash, or via the gh CLI — both of which need to be authenticated. Actually, the gh CLI is itself using Bash commands under the hood. But can Claude Code directly connect to the GitHub REST API to be faster and more efficient? curl can be used for that:
Expanded diagram showing how Claude Code uses its Bash tool (via curl, git, or gh CLI) to reach the GitHub REST API.

Why MCP exists
Every ecosystem/SaaS (like GitHub) has a separate, specific, proprietary way of connecting to it — so if Claude has to connect to multiple SaaS platforms, it would need to know all of them, which deviates Claude from its core functionality.
Can GitHub give a way to connect directly to a REST API? So Anthropic created a new protocol:
- Claude Code has an inbuilt MCP client when installed.
- GitHub/any SaaS provider provides an MCP server — basically an endpoint.
- Our job is only to connect to the MCP server; the MCP server handles the rest.
Three types of MCP server
- stdio — runs locally on the machine
- http — hosted at an HTTP location
- SSE — Server-Sent Events, a streaming protocol
All AI tools support MCP servers. The MCP client is part of the ecosystem.
Claude Code runs with an agentic harness — the LLM decides what to do, and the LLM tells Claude Code which tools to use.
12. End-to-End Automation
End-to-end automation of security review → issue creation → issue fix → PR creation → PR fix, all through agents.
The /security-review command loading its skill to audit tokenHelper.js.

Security review finding a critical hardcoded fallback JWT signing secret vulnerability in tokenHelper.js.

/create-issue
Claude using the gh CLI to file a GitHub issue for the hardcoded JWT secret vulnerability.

GitHub issue #2 detailing the hardcoded fallback JWT secret bug, its impact, and expected fix.

Now fix the issue
Claude implementing the fix: removing the hardcoded JWT secret fallback and requiring the env var explicitly.

Commit the changes
The /commit command staging, reviewing, and pushing the JWT secret fix with a detailed commit message.

GitHub activity showing the fix commit automatically linked to close issue #2.

Pull request description summarizing what changed and why for the JWT secret fix.

13. Agent Teams
Beyond single subagent delegation, Claude Code supports Agent Teams — multiple agents working the same task from different angles in parallel, then synthesizing a combined result.
Example prompt
Create an agent team to review PR #1.
Spawn three reviewers:
- Reviewer 1: focused exclusively on security implications
- Reviewer 2: focused on performance and scalability
- Reviewer 3: validating test coverage and edge cases
Have them each review independently, then synthesize findings into a single report.
Create a new branch with custom name including today's date.
Merge and close the PR with comment.
User prompt instructing Claude to spawn three specialized reviewer agents to review and merge PR #1.

The 3 agents are invoked in the background:
Claude launching three specialized reviewer agents in parallel to review PR #1.

Progress log showing the security and test-coverage reviewer agents finishing with real findings.

Claude synthesizing all three reviewer agents' findings before applying fixes and merging.

Claude merging the verified fix, filing two follow-up issues, and preparing to post the review synthesis as a PR comment.

Final summary of the agent-team review: verdicts from all three reviewers and the actions taken to merge PR #1.

14. Key Takeaways
- The agentic loop (gather context → decide → act → verify) is the mental model for everything Claude Code does.
-
CLAUDE.md and
.claude/settings.jsonare how you shape Claude's behavior and permissions for a specific project. - Commands are manual, Skills auto-trigger, and Agents run scoped, background work — often orchestrated together.
- Hooks and MCP are the two extension points: hooks intercept lifecycle events for guardrails/automation, MCP standardizes how Claude talks to external tools.
-
Session management (
/resume,/fork,/compact) is essential for working on multiple issues without polluting context or wasting tokens. - Agent Teams unlock genuinely parallel, multi-perspective work — like having three reviewers look at a PR simultaneously.
This was a genuinely comprehensive look at how far the agentic coding model has come — from a single loop reading and writing files, to fully orchestrated, permissioned, multi-agent development workflows.
Top comments (0)