TypeScript Strict Mode skill for Codex

Strict on, without a thousand non-null assertions. Codex measures error counts per strict flag, picks a rollout that fits the codebase (all at once, flag by flag, or a strict tsconfig whose include list only grows), and fixes errors by narrowing, honest types and real defaults rather than any or non-null assertions. It reports suppression counts before and after, and flags any fix that changes runtime behavior.

  • Languages
  • $typescript-strict-mode
  • 184 lines
  • 2 files
  • ~1,900 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- typescript-strict-mode

Installs to ~/.agents/skills/typescript-strict-mode/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $typescript-strict-mode, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Turns on TypeScript strict mode in an existing codebase without a big-bang rewrite - flag-by-flag rollout, a strict tsconfig ratchet over migrated files, error budgets in CI, and correct fixes for strictNullChecks, noImplicitAny, strictPropertyInitialization and noUncheckedIndexedAccess instead of any or non-null assertions. Use when a user asks to enable strict, upgrade to TypeScript 6 or 7 where strict became the default, reduce @ts-ignore usage, or fix a wave of "possibly undefined" errors.

Use it when

  • A TypeScript project still has strict off or partly off
  • An upgrade to TypeScript 6 or 7 surfaced errors because strict became the default
  • The codebase leans on any, @ts-ignore or non-null assertions
  • You want to add noUncheckedIndexedAccess after strict is clean

Use something else when

How to check the result

tsc --noEmit passes in CI including the strict ratchet config, and counts of any, @ts-expect-error and non-null assertions did not rise.

The mistake it prevents

Silencing strictNullChecks errors with the non-null assertion operator, which turns compile-time warnings back into runtime crashes.

~/.agents/skills/typescript-strict-mode/SKILL.md

--- name: typescript-strict-mode description: Turns on TypeScript strict mode in an existing codebase without a big-bang rewrite - flag-by-flag rollout, a strict tsconfig ratchet over migrated… license: MIT ---

Done well means: strict: true is on in the main tsconfig, CI fails on new errors, each fix encodes a real invariant (a narrowing, a default, a guard), and the number of any, ! and @ts-expect-error did not go up to get there.

Before changing anything

  1. Read AGENTS.md for the typecheck command (tsc --noEmit, pnpm typecheck, vue-tsc, project references with tsc -b).
  2. Inspect every tsconfig (tsconfig*.json, extends chains, per-package configs in a monorepo) and the TypeScript version in the lockfile.
  3. TypeScript 6.0 made strict the default when it is not set, and 7.0 (the Go-based native compiler) keeps that default. A repo upgrading with no strict key suddenly gets strict errors; set "strict": false explicitly as a stopgap and follow the rollout below.
  4. Measure per flag, so you know where the cost is:
for f in noImplicitAny strictNullChecks strictFunctionTypes strictBindCallApply \
         strictPropertyInitialization noImplicitThis useUnknownInCatchVariables; do
  n=$(npx tsc --noEmit --pretty false --$f 2>/dev/null | grep -c 'error TS')
  echo "$f $n"
done

What strict turns on

FlagTypical error volumeFix pattern
noImplicitAnyHigh in JS-ported codeAnnotate parameters; type JSON with interfaces or a schema
strictNullChecksHighest, most valuableNarrow, default, or change the type to be honest
strictFunctionTypesLowCallback parameter types were too wide; fix the declared type
strictBindCallApplyLowReal argument mismatches
strictPropertyInitializationMedium in class-heavy codeInitialize in constructor, make optional, or declare for framework-injected fields
noImplicitThisLowArrow functions or a this: parameter
useUnknownInCatchVariablesMediumNarrow err with instanceof Error
alwaysStrictNoneEmits "use strict"
strictBuiltinIteratorReturn (5.6+)LowIterator results typed with undefined return

Recommended extras after strict is clean, in this order: noImplicitOverride, noFallthroughCasesInSwitch, noUncheckedIndexedAccess (high volume, high value), exactOptionalPropertyTypes (only for libraries that care about undefined vs missing).

Rollout strategy

CodebaseStrategy
< 200 errors totalTurn strict: true on and fix everything in one PR series
Errors concentrated in one or two flagsEnable flags one at a time in the main config, cheapest first; strictNullChecks last
Thousands of errors spread everywhereStrict ratchet config over migrated files (below), or file-level opt-out with typescript-strict-plugin (// @ts-strict-ignore headers that only shrink)

Strict ratchet with two configs:

// tsconfig.strict.json — CI runs this too
{
  "extends": "./tsconfig.json",
  "compilerOptions": { "strict": true, "noEmit": true },
  "include": [
    "src/lib/**/*",
    "src/features/billing/**/*"
  ]
}
{ "scripts": { "typecheck": "tsc --noEmit && tsc -p tsconfig.strict.json" } }

Caveat: files in include pull in their imports, so errors in non-migrated dependencies appear too. Migrate leaves first (utils, types, API clients), then work upward. Each PR adds paths to include; none removes them. When include covers src, flip the main config and delete the extra one.

Error-budget alternative: commit the current error count and fail CI if it rises.

count=$(npx tsc --noEmit --pretty false -p tsconfig.strict-all.json | grep -c 'error TS')
test "$count" -le "$(cat .strict-budget)" || { echo "strict errors rose to $count"; exit 1; }

Fixing errors properly

Possibly undefined / null (TS18048, TS2532, TS18047)

// Bad: hides the bug
const user = users.find(u => u.id === id)!;

// Good: decide what absence means
const user = users.find(u => u.id === id);
if (!user) throw new NotFoundError(`user ${id}`);

// Good: a real default
const pageSize = options.pageSize ?? 50;

// Good: the type was lying; make it honest at the source
interface Order { shippedAt: Date | null }   // and handle null where it is read

Use ! only where an invariant the compiler cannot see is guaranteed nearby (for example right after a Map.has check on the same key), and prefer restructuring even then: const v = map.get(k); if (v !== undefined) ....

Implicit any (TS7006, TS7031)

Annotate function parameters; let return types and locals infer. For external JSON, do not write any: declare an interface and validate at the boundary (Zod, Valibot, or a type guard).

Class properties (TS2564)

class Service {
  private client: HttpClient;              // initialize in constructor
  private cache?: Map<string, Item>;       // genuinely optional
  declare readonly config: Config;         // set by a framework/decorator, no emit
  constructor(client: HttpClient) { this.client = client; }
}

Definite assignment name!: string is acceptable only for fields assigned by frameworks (Angular @Input, ORM entities) and is better replaced with the framework’s own pattern.

Catch variables (TS18046)

try { await save(); }
catch (err) {
  const message = err instanceof Error ? err.message : String(err);
  logger.error({ err }, message);
  throw err;
}

noUncheckedIndexedAccess

arr[i] and record[key] become T | undefined. Prefer for...of, .at() with a check, destructuring with defaults, or Map for dynamic keys. Do not blanket-add !.

Suppressions policy

  • @ts-expect-error with a reason, never @ts-ignore: it fails when the error disappears.
  • Enforce with typescript-eslint: @typescript-eslint/ban-ts-comment (require descriptions), no-explicit-any, no-non-null-assertion as warn during migration, error after.
  • Count suppressions before and after; report both numbers.
rg -c '@ts-expect-error|@ts-ignore|: any\b|as any\b|!\.' src | awk -F: '{s+=$2} END {print s}'

Gotchas

  • skipLibCheck: true hides errors in .d.ts files, including your own generated ones. Keep it on for speed but run one CI job without it if you publish types.
  • Turning on strictNullChecks changes inferred types (find returns T | undefined), which can surface errors in files nobody touched. Expect fan-out.
  • Frameworks with their own checker (vue-tsc, svelte-check, Angular strictTemplates) need the same flags enabled there, or templates stay unchecked. On TypeScript 7 these tools may still run a 6.x compiler side by side; keep both on the same tsconfig.
  • Project references (tsc -b): every referenced project needs the flag; the root config does not propagate compiler options to them.
  • Do not change runtime behavior while fixing types. A new throw or default is a behavior change; call it out in the PR and add a test.

Verification

  • tsc --noEmit (and the strict ratchet config) exits 0 in CI.
  • Suppression and any counts are reported before and after; they did not rise.
  • Tests pass; any fix that added a guard or default has a test for the absent case.

PR summary template

**Scope:** strictNullChecks for src/features/billing (+38 files in tsconfig.strict.json)
**Errors fixed:** 214 (narrowing 120, honest types 61, defaults 25, guards with tests 8)
**Suppressions:** @ts-expect-error 3 -> 3, non-null assertions 41 -> 12, any 17 -> 9
**Behavior changes:** NotFoundError now thrown in getInvoice when customer missing (test added)

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "TypeScript Strict Mode"
  short_description: "Strict on, without a thousand non-null assertions"
  default_prompt: "$typescript-strict-mode Turn on strict mode in this TypeScript repo without one giant PR"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem