Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
158968 stories
·
33 followers

Building API Reference Documentation From Code, Part 1: The Pipeline

1 Share

At endjin, we maintain Corvus.JsonSchema, an open source high-performance JSON library for .NET. We generate API reference documentation for 16 libraries across two engine versions (V4 and V5), spanning JSON Schema validation, query languages (JSONata, JMESPath, JsonLogic, JSONPath), mutable documents, YAML conversion, and JSON Patch. (We deliberately exclude the 7 V4 JSON Schema dialect libraries - Draft 4/6/7/201909/202012/OpenApi30/31 - because they contain thousands of generated types with repetitive patterns that would add ~25,000 pages and make the build impractically slow.)

That's a lot of public API surface to document. We needed API reference documentation that would stay accurate as the code evolved, support both engine versions side by side, and be enriched with hand-written examples and descriptions. It also needed to avoid requiring a human to manually update about 8,800 pages every time a method signature changed.

This post describes the system we built: a custom documentation pipeline that generates API reference directly from compiled assemblies and XML doc comments, produces a searchable static site with source links and TFM availability badges, and runs as part of our CI build.

The constraints

Before building anything, we listed what we needed:

  • Multi-assembly, multi-version: 8 libraries per engine version, with cross-assembly type links (e.g., a method in Corvus.Text.Json.Patch that takes a JsonElement defined in Corvus.Text.Json).
  • Version switcher: a V4 ↔ V5 toggle on the API landing pages, since both engines ship in the same toolchain and many users are evaluating which to adopt.
  • TFM availability: clear indication of which types and members exist on netstandard2.0 vs netstandard2.1 vs net10.0, since the library multi-targets and some APIs are only available on newer frameworks.
  • Regeneration-safe enrichment: hand-written namespace descriptions and type-level usage examples that survive when the generator runs, because useful documentation requires human context that can't be extracted from XML doc comments alone.
  • Source links: "View Source" links pointing at the exact file and line on GitHub, derived from PDB metadata rather than heuristics.
  • CI integration: the full documentation build runs in GitHub Actions alongside the library build and tests.

We evaluated existing tools. The combination of multi-assembly cross-linking, dual-version page generation, TFM badge scanning, and regeneration-safe enrichment we needed would have required substantial customisation of any off-the-shelf solution. Since we already had a static site generator (Vellum), we decided to build a focused tool that did exactly what we needed.

The core generator

The heart of the system is XmlDocToMarkdown, a C# console application that reads XML documentation files and compiled assemblies, and produces the artefacts needed to render API reference pages.

What goes in

For each library, the tool receives three inputs:

  1. XML documentation file (Corvus.Text.Json.xml) - the standard output from <GenerateDocumentationFile>true</GenerateDocumentationFile>, containing <summary>, <param>, <returns>, <remarks>, <example>, and <exception> elements for every documented member.

  2. Compiled assembly (Corvus.Text.Json.dll, net10.0 build) - inspected via System.Reflection to discover the actual public API surface: types, members, generic constraints, inheritance, interface implementations.

  3. Companion assemblies (netstandard2.0 and netstandard2.1 builds, optional) - scanned to determine which types and members are available on each target framework.

For multi-assembly documentation, these triplets are repeated. The V5 build step constructs the argument pairs for all 8 libraries:

$v5ToolArgs = @()
foreach ($proj in $v5Projects) {
    $binDir = Join-Path $v5SrcDir "$proj\bin\Release\net10.0"
    $xmlFile = Join-Path $binDir "$proj.xml"
    $dllFile = Join-Path $binDir "$proj.dll"
    $ns20Dll = Join-Path $v5SrcDir "$proj\bin\Release\netstandard2.0\$proj.dll"
    $ns21Dll = Join-Path $v5SrcDir "$proj\bin\Release\netstandard2.1\$proj.dll"
    if ((Test-Path $xmlFile) -and (Test-Path $dllFile)) {
        $v5ToolArgs += "--xml", $xmlFile, "--assembly", $dllFile
        if (Test-Path $ns20Dll) { $v5ToolArgs += "--ns20-assembly", $ns20Dll }
        if (Test-Path $ns21Dll) { $v5ToolArgs += "--ns21-assembly", $ns21Dll }
    }
}

What comes out

From those inputs, the tool generates several kinds of output:

Output Purpose V5 count
Namespace markdown One page per namespace with a type listing table 21
Type markdown One page per public type with signature, docs, member tables ~800
Member markdown One page per method overload group, property, operator, etc. ~2,500
Taxonomy YAML Metadata for Vellum (our static site generator) to route and render each page ~3,300
Razor views API index page with namespace cards, hierarchical sidebar partial 2
Search index JSON file consumed by Lunr for per-version type and member search 1

The output directory has a flat file structure, with naming conventions that encode the namespace and type hierarchy:

Api-v5/
├── corvus-numerics.md                              # Namespace page
├── corvus-numerics-bignumber.md                    # Type page
├── corvus-numerics-bignumber.parse.md              # Member page (method)
├── corvus-numerics-bignumber.op-addition.md        # Member page (operator)
├── corvus-text-json.md                             # Namespace page
├── corvus-text-json-jsonelement.md                 # Type page
├── corvus-text-json-jsonelement.clone.md           # Member page
├── corvus-text-json-jsonelement.createbuilder.md   # Member page
├── ...
├── namespaces/
│   ├── Corvus.Numerics.md              # Hand-authored namespace description
│   ├── Corvus.Text.Json.md             # (survives regeneration)
│   └── ...                             # 21 files
├── examples/
│   ├── corvus-text-json-jsonelement.md # Hand-authored type example
│   └── ...                             # 25 files
└── sidebar.html                        # Pre-rendered sidebar fragment

Each generated type page includes the full signature, XML doc summary, member tables with links, and (where available) source links and hand-authored examples. For example, the generated page for JsonElement.Clone looks like this:

## Definition
**Namespace:** Corvus.Text.Json
**Assembly:** Corvus.Text.Json.dll
**Source:** [JsonElement.cs](https://github.com/.../JsonElement.cs#L2288)

## Clone() {#clone}
Get a JsonElement which can be safely stored beyond the lifetime
of the original JsonDocument.

​```csharp
public JsonElement Clone()
​```

### Returns
[`JsonElement`](/api/v5/corvus-text-json-jsonelement.html)
A JsonElement which can be safely stored beyond the lifetime
of the original JsonDocument.

Each page also gets a companion taxonomy YAML file that tells the static site generator how to route and render it:

ContentType: application/vnd.endjin.ssg.page+yaml
Title: "JsonElement"
Template: api/v5/api-page
Navigation:
  Title: "JsonElement"
  Description: "Represents a specific JSON value within a JsonDocument."
  Parent: /api/v5
  Url: /api/v5/corvus-text-json-jsonelement.html
  Rank: 109
ContentBlocks:
  - ContentType: application/vnd.endjin.ssg.content+md
    Spec:
      Path: ../../content/Api-v5/corvus-text-json-jsonelement.md

In total, a single pipeline run produces about 8,800 API reference pages. That includes 3,300 for V5 and 5,500 for V4.

The complete pipeline

The documentation build script orchestrates everything in a single PowerShell pipeline:

Step What it does
0 Copy hand-authored source files (overviews, taxonomy seeds)
1a Build 8 V5 libraries (Release, net10.0 + netstandard2.0 + netstandard2.1)
1b Build 8 V4 libraries (Release, net10.0 + netstandard2.0)
2a Generate V5 API pages (markdown, taxonomy, views, search index)
2b Generate V4 API pages
3 Generate recipe content from 42 ExampleRecipes
4 Generate docs content from source documentation via descriptors
5 Install Vellum SSG
6 Run Vellum to render the core site
7 Compile SCSS and copy API search indices/sidebars
8 Build site-wide Lunr search index
9 Build and publish interactive playgrounds
10 Check for broken links (lychee)
11 Rewrite root-relative paths for GitHub Pages subpath hosting

The link checker (step 10) runs before step 11's path rewriting, so root-relative links like /api/v5/corvus-text-json-jsonelement.html resolve directly against the .output/ directory structure. Any broken internal link fails the build.

CI integration

The documentation build is integrated into our CI pipeline as a PostBuild task:

task PostBuild BuildWebSiteLocal

task BuildWebsite {
    $websiteDir = Join-Path $here "docs\website"

    $websiteBuildArgs = @{ SkipDotNetBuild = $true }

    if ($VellumDownloadToken) {
        $websiteBuildArgs += @{ VellumDownloadToken = (ConvertTo-SecureString $VellumDownloadToken -AsPlainText) }
    }

    $basePathPrefix = $env:BUILDVAR_BasePathPrefix
    if ($basePathPrefix) {
        $websiteBuildArgs += @{ BasePathPrefix = $basePathPrefix }
    }

    & (Join-Path $websiteDir "build.ps1") @websiteBuildArgs
}

task BuildWebSiteLocal -If { $BuildWebsite } BuildWebsite

The BuildWebSiteLocal wrapper means the website only builds when $BuildWebsite is set. You pass this flag explicitly for local documentation builds. In CI, the BuildWebsite task is invoked directly by the workflow. Either way, -SkipDotNetBuild means it reuses the already-compiled binaries from the main build step.

We also run a separate documentation code sample catalog check in our PreBuild task, which catches drift between documentation markdown and its code sample inventory before the build even starts.

Following one type through the system

To make this concrete, here's what happens when someone adds a new public method to JsonElement:

  1. The developer writes the method with XML doc comments (summary, params, returns, exceptions).
  2. CI builds the library - dotnet build produces the updated .dll and .xml.
  3. The XmlDocToMarkdown tool runs. It loads all 8 V5 assembly/XML pairs, then inspects each assembly.
  4. For JsonElement, it finds the new method in the assembly metadata, matches it to its XML doc entry, and checks whether the method exists in the netstandard2.0 and netstandard2.1 builds.
  5. It regenerates corvus-text-json-jsonelement.md (the type page) with the new method in its member table, and creates or updates corvus-text-json-jsonelement.{method-slug}.md (the member detail page) with the full signature, parameter docs, return type, exceptions, and source link.
  6. The hand-authored file examples/corvus-text-json-jsonelement.md is loaded and merged into the type page - untouched by the regeneration.
  7. The search index includes the new method. The sidebar includes it. The link checker validates all new links.

No one manually edited a documentation page. The developer wrote code and XML doc comments. Everything else was automated.

So that's the shape of the system: XML docs and compiled assemblies go in, a fully navigable documentation site comes out, and hand-authored enrichments survive regeneration. If you want to adapt the approach for your own project, the XmlDocToMarkdown source and the build pipeline are the places to start.

In Part 2, we'll go under the hood: how the cross-assembly linking, PDB-based source links, TFM scanning, enrichment merging, and search indexing actually work.



Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

Remote control: steering your AI coding session from your phone

1 Share

There's a moment when you're deep into an agentic coding session and you have to leave your desk (time to catch my train!). Normally that means the session just sits there, waiting, until you're back at your keyboard. Both Claude Code and GitHub Copilot CLI recently shipped a feature that fixes exactly this: you keep the session running locally, but you can check in, approve tool calls, and keep steering it from your phone or any browser.

Let's look at how each one works.

Claude Code: /remote-control

In Claude Code, you enable this with a slash command inside a running session:

/remote-control

or the short form:

/rc

You can also start a session already remote-enabled:

claude --remote-control

or run a dedicated server process that can host multiple concurrent sessions:

claude remote-control

Whichever way you start it, Claude Code prints a session URL (and lets you press spacebar to show a QR code) that connects to claude.ai/code or the Claude app. From there you get a live view of the terminal: connection status, tool activity, and the ability to send messages back in.

Remark: the architecture is worth understanding. Your local Claude Code process makes an outbound HTTPS connection to the Anthropic API and polls it for instructions — it never opens an inbound port. Your phone doesn't execute anything or touch your files directly; it just renders the conversation and sends prompts. Execution, file access, and MCP servers all stay on your machine.

A few practical details:

  • The local process has to keep running. Close the terminal or quit VS Code, and the session ends.
  • If your machine loses network for more than roughly 10 minutes, the session times out and you'll need to run claude remote-control again.
  • Some commands, like /plugin or /resume, are local-only — they won't work from a remote client.
  • Server mode has a nice trick: claude remote-control --spawn worktree gives each on-demand session its own git worktree, so parallel sessions don't fight over the same files. You switch between them in the Claude app like chat threads, with push notifications when one finishes or needs a decision.

GitHub Copilot CLI: /remote

Copilot CLI has the same idea, just named differently. Inside a running session:

/remote on

Or start the session already remote-enabled:

copilot --remote

Or make it the default for every interactive session by adding this to ~/.copilot/settings.json:

{
  "remoteSessions": true
}

(Override that per session with --no-remote.)

Once enabled, Copilot CLI prints a link to the session on GitHub.com.


Sign in with the same account that started the session and you get a live, steerable view, not just read-only output.

You can also find it without the link: GitHub.com → Copilot icon → "Agent sessions," or the Agents tab of the repo you started the session in.

Remark: don't confuse this with regular session syncing. Copilot CLI sessions sync to your GitHub account by default and show up as view-only on GitHub.com and GitHub Mobile but you can't steer those. Only sessions with remote control explicitly enabled are steerable.

I asked a question on the Github website:

And the agent starts to work on it locally:

On mobile, it's the same experience via GitHub Mobile: tap the Copilot button, find your session under "Agent sessions." For quick access, run /remote in the session to redisplay the details, then press Ctrl+E to toggle a QR code.

One thing Copilot CLI has that I like: /keep-alive. Your laptop going to sleep kills the session just like it would with Claude Code, so you can tell it explicitly to stay awake:

/keep-alive on       # never sleep while the session is active
/keep-alive busy     # only stay awake while Copilot is actively working
/keep-alive 8h       # stay awake for a fixed duration

And if you resume a session later with copilot --continue or copilot --resume, remote control is automatically re-enabled. You don't need to pass --remote again.

Side by side

Both tools solve the same problem the same way: execution stays local, only the conversation travels.

  • Enable inline: /remote-control (or /rc) in Claude Code, /remote on in Copilot CLI.
  • Enable at launch: --remote-control vs. --remote.
  • Always-on default: a settings toggle in Claude Code's /config, or "remoteSessions": true in Copilot CLI's settings.json.
  • Remote client: claude.ai/code or the Claude app for Claude Code; GitHub.com or GitHub Mobile for Copilot CLI.
  • Awake-keeping: Claude Code relies on you keeping a terminal session alive; Copilot CLI gives you a built-in /keep-alive command with fine-grained durations.
  • Parallel sessions: Claude Code's server mode with git worktrees per session; Copilot CLI doesn't need a dedicated server mode since every session syncs to your GitHub account by default.

Which one you reach for probably just comes down to which tool you're already running. If you're bouncing between both like I do, it's worth knowing that both tools have this functionality built-in.

That's it! Two different agent harnesses, same remote control idea.

More information

Read the whole story
alvinashcraft
37 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

A Better Fire Alarm Is Still a Fire

1 Share

A better fire alarm is still a fire. Many SQL Server incidents I get called about were preventable with controls already available in the client’s environment. Here is the audit I wish had been run before the pager rang, complete with the T-SQL.

A Better Fire Alarm Is Still a Fire alarm-in-amber-light

I finished a root cause analysis recently that I was quietly proud of. It had everything a respectable incident document is supposed to have: a timeline to the second, the plan regression that started it, the parameter behind the regression, and the deployment three weeks earlier that changed the parameter.

The document was accurate. That was the uncomfortable part. Not one important fact required hindsight. Every warning had existed before the outage. I had not solved a mystery. I had written an excellent account of an avoidable event.

A Better Fire Alarm Is Still a Fire knowable-in-advance

So this post is the other document, the one that should exist before the incident. These are eight checks I now run on every engagement, why each one matters, and the T-SQL to see where you stand. The first pass takes about an hour. Testing and implementing any change still belongs in normal change control.

A quick note. Details are blended across engagements and changed so nothing identifies a client. Test everything below outside production first, as you would with anything you read on the internet.

There is an easy way to ruin this audit before it begins: assume every edition can do every trick. Record the exact version and edition first. Automatic tuning remains an Enterprise feature in boxed SQL Server. Developer edition through SQL Server 2022, and Enterprise Developer in SQL Server 2025, provide the Enterprise feature set for non-production development. Resource Governor is available in Enterprise and Developer through SQL Server 2022. SQL Server 2025 makes it available in Enterprise, Enterprise Developer, Standard, and Standard Developer. Azure SQL offerings have different support rules.

SELECT SERVERPROPERTY('ProductVersion')      AS product_version,
       SERVERPROPERTY('ProductMajorVersion') AS major_version,
       SERVERPROPERTY('Edition')             AS edition,
       SERVERPROPERTY('EngineEdition')       AS engine_edition;

The audit queries are read-only, but read-only does not mean permission-free. Requirements vary by view and SQL Server version. Common permissions include VIEW DATABASE STATE or VIEW DATABASE PERFORMANCE STATE for database-scoped information, and VIEW SERVER STATE or VIEW SERVER PERFORMANCE STATE for instance-scoped information. Changing a setting requires separate permission and change approval.

Checks 1 and 2 are database-scoped. The detailed statistics and table-footprint queries in checks 5 and 6 are also database-scoped, so run them in each writable user database you intend to audit. The remaining queries examine instance-wide configuration, tempdb, Resource Governor, or all online user databases.

The One-Hour Prevention Pass

Check Failure class First evidence to collect
1 Plan regression Automatic tuning state and current recommendations
2 Missing performance history Query Store state, capacity, and capture freshness
3 Parallel worker pressure Cost threshold, MAXDOP, waits, and workload evidence
4 tempdb allocation contention File layout and sustained allocation-page waits
5 Cardinality estimate drift Statistics settings, freshness, sampling, and plan estimates
6 Large-table deployment risk Row count, space, lock impact, log impact, and rollback
7 Workload collision Resource Governor policy and session classification
8 Slow-building outage conditions Sustained blocking and transaction-log pressure

Check 1: Automatic Plan Correction, With Receipts

A Better Fire Alarm Is Still a Fire prevention-audit

This is the one that annoys me most on supported editions. The capability has existed since SQL Server 2017, yet I still meet systems where nobody has even checked its state. Enabling it is one statement. Deciding to enable it is still a change, and those are not the same thing.

When SQL Server identifies an eligible query plan choice regression, automatic plan correction can force the last known good plan, verify the result, and undo the force if performance does not improve. It does not catch every regression, but it can make a useful class of plan incidents self-correcting.

SELECT name,
       desired_state_desc,
       actual_state_desc,
       reason_desc
FROM sys.database_automatic_tuning_options;

If actual_state_desc is OFF, do not jump straight to the ALTER statement. Read reason_desc, confirm that the version and edition support the feature, and verify that Query Store is healthy. A single command can enable the feature, but it cannot create the history the feature needs.

ALTER DATABASE CURRENT
SET AUTOMATIC_TUNING (FORCE_LAST_GOOD_PLAN = ON);

Automatic plan correction requires Query Store to be read-write, which is check 2. After a week, ask the database for receipts instead of assuming that ON means useful:

SELECT type,
       reason,
       score,
       execute_action_initiated_by,
       execute_action_initiated_time,
       revert_action_initiated_by,
       revert_action_initiated_time,
       JSON_VALUE(state, '$.currentValue')                        AS current_state,
       JSON_VALUE(state, '$.reason')                              AS state_reason,
       JSON_VALUE(details, '$.planForceDetails.queryId')          AS query_id,
       JSON_VALUE(details, '$.planForceDetails.regressedPlanId')  AS regressed_plan,
       JSON_VALUE(details, '$.planForceDetails.recommendedPlanId') AS recommended_plan,
       JSON_VALUE(details, '$.implementationDetails.script')      AS script
FROM sys.dm_db_tuning_recommendations
ORDER BY score DESC;

The score is the estimated value or effect of the recommendation on a scale from 0 to 100, with larger values considered better. If you are not ready to enable automatic correction, leave it off and review this DMV for a fortnight. SQL Server can still identify potential regressions when the option is disabled. Treat each recommendation as a lead to investigate, not a guaranteed future outage. The DMV is not persisted, so a Database Engine restart clears its recommendations. If the history matters, collect it elsewhere.

Check 2: Query Store Is On, but Is It Alive?

SQL Server 2022 enables Query Store by default for newly created databases. Databases restored from earlier versions, and databases carried through an in-place upgrade, retain their previous setting. In every case, on is not the same as working.

The failure I find most often is a Query Store sitting in READ_ONLY because its storage filled months ago. It stopped collecting quietly, nobody was alerted, and the first person to notice is the person who desperately needs yesterday’s history. That is a terrible time to discover that the security camera has not been recording.

A Better Fire Alarm Is Still a Fire query-store-history

SELECT actual_state_desc,
       desired_state_desc,
       readonly_reason,
       current_storage_size_mb,
       max_storage_size_mb,
       query_capture_mode_desc,
       size_based_cleanup_mode_desc,
       stale_query_threshold_days
 FROM sys.database_query_store_options;

On a database with an active workload, also confirm that Query Store contains recent runtime data:

SELECT MAX(last_execution_time) AS latest_captured_execution
FROM sys.query_store_runtime_stats;

A null or unexpectedly old timestamp on a busy database is a reason to inspect the capture policy and Query Store health. Interpret it against the workload and collection interval, because an idle database should not produce fresh executions.

If actual_state_desc and desired_state_desc disagree, something forced it read-only and readonly_reason tells you what. It is a bitmask. 65536 means the storage size was exceeded, which is the common one.

Here is a sensible starting configuration, not a universal recipe. Adjust the capacity and retention to the workload, then monitor them:

ALTER DATABASE CURRENT SET QUERY_STORE (
    OPERATION_MODE            = READ_WRITE,
    MAX_STORAGE_SIZE_MB       = 2048,
    QUERY_CAPTURE_MODE        = AUTO,
    SIZE_BASED_CLEANUP_MODE   = AUTO,
    CLEANUP_POLICY            = (STALE_QUERY_THRESHOLD_DAYS = 60),
    MAX_PLANS_PER_QUERY       = 200,
    INTERVAL_LENGTH_MINUTES   = 60,
    DATA_FLUSH_INTERVAL_SECONDS = 900
);

Two notes matter. QUERY_CAPTURE_MODE = ALL can fill storage with single-execution ad hoc noise on some workloads. Also, put an alert on the state. A red row in an occasional audit is not an alerting strategy:

IF DATABASEPROPERTYEX(DB_NAME(), 'Updateability') = 'READ_WRITE'
   AND EXISTS (SELECT 1 FROM sys.database_query_store_options
               WHERE actual_state_desc <> 'READ_WRITE')
    RAISERROR('Query Store is not recording', 16, 1);

Run that check as a SQL Server Agent job step and configure the job to notify somebody when the step fails. RAISERROR by itself does not send an email.

While you are here, audit what is already being forced, including plans with recorded forcing failures:

SELECT p.query_id,
       p.plan_id,
       p.is_forced_plan,
       p.plan_forcing_type_desc,
       p.force_failure_count,
       p.last_force_failure_reason_desc
FROM sys.query_store_plan AS p
WHERE p.is_forced_plan = 1
   OR p.force_failure_count > 0;

A nonzero force_failure_count means plan forcing has failed at least once. The counter increments when forcing fails during recompilation, not on every execution. Inspect last_force_failure_reason_desc, the current plan, and recent compilations before assuming that the query is still protected.

Check 3: Parallelism Without a Worker Stampede

SELECT name, value_in_use
FROM sys.configurations
WHERE name IN ('cost threshold for parallelism',
               'max degree of parallelism');

Cost threshold still defaults to 5. Five is a factory default, not a recommendation and certainly not a family tradition. SQL Server considers parallel alternatives when the best serial plan’s estimated cost exceeds the threshold. That cost is an optimizer estimate, not seconds. If the threshold is too low for an OLTP workload, too many modest queries can receive parallel plans and contribute to worker pressure. CXPACKET and CXCONSUMER waits are evidence to interpret, not a diagnosis by themselves.

A Better Fire Alarm Is Still a Fire parallelism-worker-pressure

DECLARE @target_cost_threshold int = 20; -- Example only. Choose from evidence.
DECLARE @apply_change bit = 0;           -- Change to 1 only after approval.

SELECT value_in_use AS current_value,
       @target_cost_threshold AS proposed_value,
       @apply_change AS apply_change
FROM sys.configurations
WHERE name = 'cost threshold for parallelism';

IF @apply_change = 0
    RETURN;

IF @target_cost_threshold NOT BETWEEN 0 AND 32767
    THROW 50000, 'Choose a cost threshold between 0 and 32767.', 1;

DECLARE @advanced_options_was_on bit =
(
    SELECT CONVERT(bit, value_in_use)
    FROM sys.configurations
    WHERE name = 'show advanced options'
);

IF @advanced_options_was_on = 0
BEGIN
    EXEC sys.sp_configure 'show advanced options', 1;
    RECONFIGURE;
END;

EXEC sys.sp_configure 'cost threshold for parallelism', @target_cost_threshold;
RECONFIGURE;

IF @advanced_options_was_on = 0
BEGIN
    EXEC sys.sp_configure 'show advanced options', 0;
    RECONFIGURE;
END;

The script is deliberately safe by default and uses 20 only as an example. Raise the threshold in small, reviewed increments, observe a complete business cycle, and compare Query Store evidence before and after. Set MAXDOP deliberately as well, based on SQL Server version, available logical processors, NUMA layout, and workload behavior instead of assuming that 0 is suitable.

Neither setting needs a restart, but both can change plan selection across the instance. Treat them as measured workload changes, not harmless checkboxes.

Check 4: tempdb, Measure the Latch Before Adding Files

SELECT file_id,
       name,
       type_desc,
       size / 128.0                 AS size_mb,
       CAST(CASE WHEN is_percent_growth = 1
                 THEN growth
                 ELSE growth * 8.0 / 1024
            END AS decimal(18,2))   AS growth_value,
       CASE WHEN is_percent_growth = 1
            THEN 'PERCENT' ELSE 'MB'
       END                          AS growth_unit
 FROM tempdb.sys.database_files;

tempdb has accumulated enough folklore to qualify for its own mythology. Start with evidence. Look for data files sized for the workload, equal sizes and growth increments across the data files, and fixed-megabyte growth rather than percentage growth. Multiple equally sized data files are a standard starting point for allocation contention, commonly one per logical processor up to eight, but do not multiply files when the waits do not support that diagnosis.

A Better Fire Alarm Is Still a Fire tempdb-allocation-contention

To confirm allocation contention instead of diagnosing by tradition, look at what is waiting right now:

SELECT session_id,
       wait_type,
       wait_duration_ms,
       blocking_session_id,
       resource_description
FROM sys.dm_os_waiting_tasks
WHERE wait_type LIKE 'PAGELATCH[_]%'
  AND resource_description LIKE '2:%';

2:1:1, 2:1:2, and 2:1:3 are familiar first-page examples for PFS, GAM, and SGAM in the first tempdb data file. Allocation pages repeat later in every file, so those three page numbers are examples, not a complete detector. This DMV is also a point-in-time view. Sample it repeatedly and look for sustained PAGELATCH waits on tempdb allocation pages before changing the file count. One screenshot is a clue. A repeated pattern is evidence.

Check 5: Statistics, Stale Is a Diagnosis

SELECT name,
       is_auto_create_stats_on,
       is_auto_update_stats_on,
       is_auto_update_stats_async_on
FROM sys.databases
WHERE database_id > 4;

Auto create and auto update should almost always be on. The interesting setting is is_auto_update_stats_async_on. With it off, a query that triggers an automatic statistics update waits for the update before compilation continues. Turning it on avoids that synchronous wait, but the triggering query compiles with the existing statistics while the refresh runs in the background. That is a tradeoff, not a universal recommendation. On SQL Server 2022 and later, also evaluate ASYNC_STATS_UPDATE_WAIT_AT_LOW_PRIORITY to reduce lock contention from the background update.

A Better Fire Alarm Is Still a Fire statistics-estimate-gap

The settings query is instance-wide. The next query is database-scoped, so run it inside each database that matters. It finds heavily modified statistics on large rowsets that deserve inspection:

SELECT OBJECT_SCHEMA_NAME(s.object_id) AS schema_name,
       OBJECT_NAME(s.object_id)        AS table_name,
       s.name                          AS stats_name,
       sp.last_updated,
       sp.rows,
       sp.rows_sampled,
       sp.modification_counter
FROM sys.stats AS s
CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) AS sp
WHERE sp.rows > 1000000
  AND sp.modification_counter > sp.rows * 0.1
ORDER BY sp.modification_counter DESC;

The modification counter tracks changes to the leading statistics column. The 10 percent filter is a triage heuristic, not SQL Server’s internal automatic-update threshold. A large counter also does not prove that stale statistics caused the slow query. Compare rows_sampled with rows, then examine the affected query’s estimated and actual row counts. A targeted update with a higher sample rate can help when the statistics are genuinely responsible. Blanket full scans on every large table are how a maintenance job becomes the next incident report.

Check 6: Size Changes the Meaning of Safe

Most of the worst incidents I investigate trace back to a change that was technically correct and run at the wrong scale. The statement was valid. The table was enormous. SQL Server honored both facts.

You do not need a new platform to begin. You need a list, published before the change window, of the tables where nothing casual happens:

A Better Fire Alarm Is Still a Fire deployment-gate

WITH table_footprint AS
(
    SELECT object_id,
           SUM(CASE WHEN index_id IN (0, 1)
                    THEN row_count ELSE 0 END) AS row_count,
           SUM(reserved_page_count) * 8 / 1024.0 AS reserved_mb
    FROM sys.dm_db_partition_stats
    GROUP BY object_id
)
SELECT s.name AS schema_name,
       t.name AS table_name,
       f.row_count,
       f.reserved_mb
FROM table_footprint AS f
JOIN sys.tables AS t
  ON t.object_id = f.object_id
JOIN sys.schemas AS s
  ON s.schema_id = t.schema_id
WHERE f.row_count > 50000000
ORDER BY f.row_count DESC;

The row count is approximate, and reserved_mb includes the table’s indexes. Replace 50 million with a threshold that reflects your environment. That is enough to create a deployment gate. The rule fits in one sentence: no potentially blocking or size-dependent DDL against anything on this list without a written execution plan, a tested rollback path, and estimates for duration, transaction-log growth, and lock impact. That one rule has prevented more outages for my clients than many far more impressive-looking projects.

Check 7: Resource Governor for the Workload That Thinks It Owns the Server

Every shop has one. The month-end reporting workload arrives, flattens the transactional workload, gets investigated, gets explained, and then returns next month like a meeting nobody was brave enough to decline.

Governing that workload is supported where the edition permits. The values below are placeholders for a tested policy, not recommended production values. MAX_CPU_PERCENT is an opportunistic maximum that is enforced when CPU is contested, while CAP_CPU_PERCENT is a hard CPU ceiling. For ordinary disk-based workloads, MAX_MEMORY_PERCENT governs query workspace memory for the pool, not SQL Server’s total memory or buffer pool. Memory-optimized tables have additional pool behavior that must be evaluated separately.

A Better Fire Alarm Is Still a Fire resource-governor-workload-lanes

USE master;
GO

CREATE RESOURCE POOL ReportingPool
WITH (MAX_CPU_PERCENT = 25,
      CAP_CPU_PERCENT = 40,
      MAX_MEMORY_PERCENT = 25);

CREATE WORKLOAD GROUP ReportingGroup
USING ReportingPool;
GO

CREATE FUNCTION dbo.fn_ClassifyWorkload()
RETURNS SYSNAME
WITH SCHEMABINDING
AS
BEGIN
    RETURN CASE
             WHEN SUSER_SNAME() = N'DOMAIN\ReportingService'
             THEN N'ReportingGroup'
             ELSE N'default'
           END;
END;
GO

ALTER RESOURCE GOVERNOR
WITH (CLASSIFIER_FUNCTION = dbo.fn_ClassifyWorkload);
ALTER RESOURCE GOVERNOR RECONFIGURE;

This is an illustrative new configuration. If the server already has a classifier function, add the routing rule to that function instead of replacing it with this example. The classifier belongs in master and is evaluated for every new session, even when connection pooling is enabled. Reusing an existing pooled session does not create a new classification event, and existing sessions keep their current group. Keep the function simple, test with a genuinely new connection, verify the assigned workload group, and confirm dedicated administrator connection access before rollout. An overly restrictive pool can make a query run longer and hold locks longer, so validate the effect on the protected transactional workload as carefully as the effect on reporting.

Check 8: Alert on the Smoke, Not the Ashes

Nearly everybody alerts on the outage. That is useful, but late. Far fewer teams alert on the condition that has been building for several minutes while the database is still answering calls and pretending everything is fine.

A Better Fire Alarm Is Still a Fire alert-before-outage

A current blocked request that has waited more than thirty seconds is one of the highest-value signals I know. Thirty seconds is an example threshold, not a law. Tune it to the workload, poll the query from a job, retain the results, and account for maintenance that is expected to block:

SELECT r.session_id,
       r.blocking_session_id,
       r.wait_time / 1000 AS wait_seconds,
       r.wait_type,
       r.wait_resource,
       DB_NAME(r.database_id) AS database_name,
       t.text                 AS running_sql
 FROM sys.dm_exec_requests AS r
OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t
WHERE r.blocking_session_id <> 0
  AND r.wait_time > 30000;

Positive blocking_session_id values identify another session. Negative values have special meanings, including orphaned distributed transactions and latch owners that SQL Server cannot identify. In particular, -5 by itself does not prove a performance problem. This query is an early-warning signal, not a complete blocking-chain analysis.

Add transaction-log utilization and the reason that log truncation is being held. This can expose pressure caused by an active transaction, missing log backups, replication, or an availability replica before the volume is full:

SELECT d.name AS database_name,
       ls.total_log_size_mb,
       ls.active_log_size_mb,
       CAST(100.0 * ls.active_log_size_mb /
            NULLIF(ls.total_log_size_mb, 0) AS decimal(6,2)) AS active_log_percent,
       ls.log_since_last_log_backup_mb,
       ls.log_truncation_holdup_reason
 FROM sys.databases AS d
CROSS APPLY sys.dm_db_log_stats(d.database_id) AS ls
WHERE d.state_desc = 'ONLINE'
  AND d.database_id > 4
 ORDER BY active_log_percent DESC;

sys.dm_db_log_stats is available in SQL Server 2016 SP2 and later. On an availability-group secondary, the function returns only a subset of its normal columns, so missing size values should not be interpreted as zero pressure. The query shows current pressure and the truncation holdup, not the history of file-growth events. Capture autogrowth separately with Extended Events or your monitoring platform. The warning signal is often visible before the pager goes off, but only if you retain a baseline and alert on sustained abnormal values.

Why None of This Gets Done

A Better Fire Alarm Is Still a Fire where-the-energy-went

The first audit takes about an hour. Much of the remediation is configuration, code, and operating discipline using capabilities the organization already owns. Edition-specific features still need to be checked before anybody promises a change. So why is so much of the practical prevention layer unused?

Because prevention is invisible, and invisible work has no advocate.

A Better Fire Alarm Is Still a Fire invisible-work

The person who spends a quiet Thursday evaluating automatic plan correction, fixing a proven tempdb problem, testing the parallelism configuration, and writing a blocking alert has produced, from the outside, nothing at all. No incident report. No bridge call where they were heroic. Nothing dramatic to put in a review.

The person who fixes a catastrophic outage at four in the morning gets thanked in a company-wide email. The person whose preparation prevented the outage gets a quiet night and no email. I know which reward I prefer, but I also know which one organizations tend to notice.

Nobody here is behaving irrationally. The incentives point at the fire rather than at the wiring, and they have for a long time.

My practical advice is to describe prevention in the language of the incident it removes. Not “I enabled automatic tuning.” Instead, “eligible plan regressions can now be corrected automatically and verified after the force.” Not “I added an alert.” Instead, “we now detect sustained blocking while there is still time to act.” Same technical work, much clearer business value.

Where This Argument Has Limits

Two honest caveats belong here, and the second is the stronger one.

Prevention has a ceiling. You can remove known failure classes. You cannot remove the unprecedented. Monitoring and explanation will never be worth zero, and anyone promising a world with no incidents is selling you something.

You cannot prevent a failure class you have never understood. The root cause analysis is the input to the prevention work. My complaint was never that we write them. It is that we write them, file them, and do not do the next thing.

So the claim is narrower than the title suggests. Explanation is necessary. Treating explanation as the destination is the mistake.

The Section I Now Add to Every Analysis

A Better Fire Alarm Is Still a Fire report-versus-act

At the end of every incident document I write, there is now a section that is not about the incident. It is about making the same failure class less welcome next time.

It names the failure class, states what would have made it impossible rather than merely visible, and gives a rough cost. Sometimes the cost is an afternoon and a checkbox. Sometimes it is a planned project. Both are easier to fund when the cost and the failure being removed are explicit.

Some clients skip that section. A few do not, and those are the clients I eventually stop hearing from. It is the strangest form of professional success I have encountered, and I have decided to enjoy it.

The thread underneath all of this is who holds the judgment when the tooling sounds confident, which is the argument across all thirty essays in my book AI: Nobody’s in There: But we’re still in here. All thirty are free to read at pinaldave.com. If you would rather hold a copy, it is on Amazon in paperback, Kindle and audiobook.

If you take one thing from this, take the smallest one. Record the version and edition, then run the Query Store state query from check 2 against your busiest database. It takes about two minutes. If the answer is READ_ONLY, the most valuable performance history on the server may already be disappearing quietly.

This is not a story about explaining incidents better, it is a story about making the explanation a rarer thing to need.

Reference: Pinal Dave (https://blog.sqlauthority.com/), SQL Server Prevention, X

First appeared on A Better Fire Alarm Is Still a Fire

Read the whole story
alvinashcraft
50 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

What Is A Magic System? 7 Ways To Create A Spectacular Magic System For Your Novel

1 Share

What is a magic system? Learn 7 ways to create a spectacular magic system for your fantasy novel and make your world more believable.

What Is A Magic System?

In fantasy, magic makes your world go round. It is not an afterthought. It is an important literary device that shapes your writing.

Magic, and how it works, will dictate how your characters act. It does this the same way gravity makes the earth spin.

If you think this kind of thing is stupid, you are not going to be able to write about it. This is a deadly serious topic. It has made authors’ careers and it has relegated others to obscurity.

7 Ways To Create A Spectacular Magic System For Your Novel

1. Decide on its tone.

There are roughly two types of fantasy. High Fantasy and Low Fantasy.

There are roughly two magic systems. High Magic and Low Magic.

They are not related in any way. Not at all.

  1. The Lord of the Rings is a low magic setting, but it is a high fantasy setting.
  2. Discworld is a high magic setting, but it is low fantasy.

High Fantasy deals with dark lords and world ending events. Low Fantasy might deal with fixing the post office with the help of mythical creatures. (Would a centaur be a good postman?)

But, the the magic system affects the tone.

  1. Because there is so little magic in Tolkien‘s The Lord of the Rings, it is a Low Magic setting. It is difficult for the heroes to solve their problems with magic. Frodo has to walk to Mordor. He can’t just teleport or ride a magic carpet. This creates a grittier tone.
  2. Because magic is easy to use in Rowling‘s Harry Potter, it is a High Magic setting. The series is mostly about the fun things wizards can do with their magic. They have flying cars and brooms that sweep the floor for you. It makes the characters’ lives easier so they have time to deal with other problems – usually more personal ones. This leaves the tone up to the author.
  3. Forgotten Realms, created by Ed Greenwood, is High Magic, but serious, in a young adult kind of way.
  4. The Magicians, by Lev Grossman, is High Magic and often silly until it is deadly serious in very adult ways.

Writing Tip: When deciding the tone, decide how much magic you want in it, because having too much magic can make your story seem light-hearted.

Suggested reading: The 4 Pillars Of Fantasy

2. Be consistent.

Your magic system must adhere to its own logic. If this breaks down, it creates plot holes. Plot holes need thousands of words to explain away and this will bore your reader.

Nobody wants to ask the question, ‘Why didn’t they just use magic to fly away from danger?’ when clearly you show your characters doing this as a pastime in another scene. Just say that it takes ten minutes to cast the ‘Fly’ spell. Then you can still have your dramatic chase scene through the woods.

The problem with consistency in high magic settings like Doctor Strange, the Marvel Comic, is that Doctor Strange can do anything including turning back time. So, you may forget what the rules are when you have so few of them. But whatever they are, don’t break them. Rather work around them.

In Harry Potter you need a wand to do magic. Simple. If you want to create tension and keep the story consistent, Harry just needs to drop his wand. Then he’s just a boy. Problem solved.

A simple rule to your magic system like this will save you trouble later.

Writing Tip: Create an internal logic that you find easy to understand and that is simple to write about. Then reference back to this whenever you write about magic.

3. Choose carefully who can use it.

Sometimes magic is what makes a character special. In children’s books, where you want a bland protagonist the kids can project themselves onto, magic should be restricted to the main cast only.

On the other hand, in some settings, it’s fun if everyone has some magic. Perhaps, cell phones are all made by two or three competing Wizards? Perhaps Samsung the Blue and Applesen the Greedy have a bet going to see who can make more muggles addicted to Instagram.

Magic worlds work in two ways.

One where everybody in the world knows it’s there.
OR
One where it’s a secret.

This will affect how people view your characters. In the Forgotten Realms, outside the Archmage Elminster’s tower, these warnings magically appear as a person walks towards it:

  1. ‘Trespassers could die a quick and certain death or they could be invited in for stew. Thank you for thinking better of disturbing my privacy.’
  2. ‘An archmage often can react poorly to interruption. – Please reconsider before it is too late.’
  3. ‘No Trespassing. Violators should notify next of kin. Have a pleasant day.’
  4. ‘Rumours of spike-filled pits along this path are almost totally false. Thank you for your caution.’
  5. ‘This ancient path/Is cracked and paved/With visitors who/Could not behave.’

But in The Magicians, magicians hide from the public by often living double lives.

In the first setting, it clearly pays to advertise that a wizard lives here. In the second, it might be illegal in wizard society to even tell your extended family that magic is real.

Writing Tip: If in doubt, write magic as if it is a rare power. This automatically makes your readers see it as important, mystical, and interesting. It also allows them to engage with childlike wish fulfilment. This helps them buy into the setting.

4. Tell us where Magic comes from.

Magic can come from three sources:

  1. It can be part of the world. Like sunlight or oxygen. You might need training to turn sunlight into electricity, but anybody with a solar panel can. This is good for world building. It lets you tell the reader how your world’s magic is put together.
  2. It can come from God or small “g” gods. Think holy water. Think priests fighting demons with flaming swords. In this case, magic must be earned by faith. This style is nice for Christian Horror themes. It can also be played for laughs like the Angel Demon buddy cop duo in Neil Gaiman and Terry Pratchett‘s Good Omens.
  3. It can come from a person. Perhaps, you can only use as much magic as you have energy in your body. Perhaps, you are born a sorcerer because you are descended from a dragon/angle/eldritch horror. In this case writers can create a mystery of finding out what makes the protagonist special.

Note: A sorcerer is a source of magic.

Writing Tip: If you don’t have a story goal in mind finding out where magic comes from is a perfect way to give your characters something to strive for.  Alternatively, use this as a big reveal that tells us something important about your world.

5. Play with magic.

It you have magic and you don’t use it, what’s the point?

Have fun with it. Make fantastic creatures and enchanting landscapes. Tell us about how gnomes create black holes to suck up their trash. Or how an elf spent a thousand years talking to a tree, because he was just too polite to leave.

Have your character use it in ways that will either put wonder into your reader or at least make them laugh. Maybe even cry?

Writing Tip: People read about magic because it is entertaining. It has no real world value. So, if your magic is boring you are failing at a basic aspect of magic fantasy writing. If you are struggling to invent playful ways to use magic, ask a small child to explain how their phone works. Or even better your grandmother.

6. Make sure you need it.

Don’t have magic just to have it.

  1. Magic is what makes worlds like Harry Potter worth reading. It enhances the enjoyment of the reader by making character moments more meaningful.
  2. On the other hand, A Game of Thrones would be just as good without it. This is because it is a character-driven plot. The dragons and snow zombies could just as well be big siege weapons and northern barbarians.
  3. The Lord of the Rings depends on magic to drive the plot. The magic ring starts the quest. It warps characters’ minds. Makes them turn evil. It needs to be destroyed to make the world right again. At every twist in the plot magic drives our characters onward.

Writing Tip: Sometimes, it is more interesting to have a reason to want magic than to be able to use magic. Your character’s motivations come before all other considerations. Magic is just a plot device to serve your story needs.

And you need to write something people want to read.

7. Know who to steal ideas from.

To really know how to make a magic system you need to steal from the best.

So, don’t start with them.

  1. Read pulp fantasy. R. A. Salvatore and Ed Greenwood write in the Forgotten Realms Dungeons and Dragons setting. It is a well put together tried and true magic world with clear rules and named spells you can look up in tables. It is a perfect starting point. (Maybe go to a comic book store and buy the rules to Dungeons and Dragons.)
  2. Then move on to J. K. Rowling‘s Harry Potter to see how you can have fun with magic.
  3. Read C. S. Lewis for a religious magical world. You can learn how to write about God without upsetting people here.
  4. Tolkien is a good place to go to learn how to use magic sparingly and how to make it seem special.
  5. Move on to Neil Gaiman to learn how to write magical landscapes. Stardust is light-hearted and American Gods is not. Both are good.
  6. Robin Hobb is an example of how to make a magic world and system that seems more real than our own.
  7. Terry Pratchett will teach you that funny is not the opposite of serious. Unfunny is the opposite of funny. You can use the Discworld books as a reference for how to turn traditional magic ideas on their heads. What happens if a girl identifies and a wizard and not a witch? See Equal Rites.

Writing Tip: I’ll paraphrase Terry Pratchett here. Writers are readers who have read so much that they start to overflow. Nobody has ever become worse at writing by reading more.

The Last Word

As I said at the beginning: magic makes your world go round. A great magic system can make your fantasy world feel real, but it needs to work with your story. Give your magic rules and limits, and think about how it affects your characters and their choices. Get this right, and your magic can add wonder, conflict, and plenty of possibilities to your novel.

[Top Tip: Learn how to write fantasy. Buy The Fantasy Workbook]

Image by Lothar from Pixabay


by Christopher Luke Dean (Secretly an alien from a long lost race of time travellers on earth to preserve the space-time continuum by writing listicals.) Christopher writes and facilitates for Writers Write.

More posts from Christopher:
  1. A Complete Glossary Of Terms For Fantasy Writers
  2. A Complete Glossary Of Terms For Science Fiction Writers
  3. Writers Write | Story Generator
  4. Things That Make Me Want To Write
  5. What You Could Be Reading Right Now
  6. Horror Movies Are Popular (& Why You Should Write Them)
  7. The Rule Of Two For Writers
  8. The Best Fantasy Map Hacks
  9. What Is Grimdark Fiction & Where Did It Come From?
  10. Brandon Sanderson’s 3 Rules For Magic

Top Tip: Sign up for our free daily writing links.

The post What Is A Magic System? 7 Ways To Create A Spectacular Magic System For Your Novel appeared first on Writers Write.

Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

The New Technical Debt

1 Share

Is AI code debt the new technical debt? As I keep learning, generating code and examining it – it’s worse.

In the old days, we worked hard to create technical debt. Just kidding, it was easy.

Technical debt has many definitions, and origin stories. But one thing is common: We leave the code as it is, knowing it could be better. The debt is the gap of effort of making it better.

And why the gap? Because we know we’ll see that code again. And when we do, it’ll be hard to change. Better code would have made it easier.

Now we’re in the age of genies. They can write any code. They can change any code. Do we need to worry about AI code debt?

Sure we do.

AI code debt is exactly the same as technical debt – we’re leaving the code as it is, knowing it could be better. The gap is still the effort of making it better.

But this time the gap is a lot bigger. There’s a lot more code, it’s probably a lot more complex, and some of it – let’s be frank – is code we didn’t review. So the gap is a lot bigger than we guess.

But that’s a bot problem, right? The code agent will deal with all the needed changes. We don’t need to even look at the code.

Nah, you know you will. You know what code is generated, and it’s not how you would have written it. And coding agents have the same problem of making sense of complex code bases as us.

And they’ll make mistakes.

Can the code agent clean the code up by itself?

Cleaning is not just “make it readable”. It’s preparing it for more changes, reducing dependencies and isolating interfaces – all in the context of future plans. And the agent doesn’t have this context.

In fact, it will assume another context, and we get stuck with the code and the assumptions.

How do you pay down AI code debt?

Same as always: find the code you know you’ll touch again, and make that code easier to change. The problem starts when you don’t know where that code is.

Because, you didn’t write it. And you didn’t review all of it. If any.

Putting a price on technical debt before was an exercise in imagination. Now it’s pure fantasy.

How do you avoid it next time?

First know what code was generated. And for that you need to enforce smaller code generation.

If you don’t enforce it, you’ll have a lot more to review. And if that happens you won’t review it all. It’s a human thing.

Then, you can wish the genie to refactor it to take the shape you want. Genies are good at transformations. And if the genie breaks something, your tests will tell you.

You do have tests, right?

The old technical debt was based on maintenance work. Now we have bigger maintenance queued up, along with risks of unverified code. Not cool.

So, first, we need to be aware of the AI code debt – we’re creating code that will cost a lot more to maintain, than “regular” code.

Before, we thought “it’s ok, we’ll take care of that later”, and then, when the bill came it was a lot more than we thought.

Now? Expect a much bigger one.

Questions people ask about AI code debt

What is AI code debt?

AI code debt is technical debt created by generated code: you leave the code as it is, knowing it could be better, and the debt is the effort of making it better. The difference is scale. There’s more of it, it’s more complex, and some of it was never reviewed.

Is AI code debt different from technical debt?

Not in kind, only in size and visibility. The old debt was something you put there and remembered. This debt arrived while you were reading something else, so you don’t know where it is.

Can an AI agent clean up its own code debt?

Not on its own. Cleaning up means preparing code for changes you plan to make, and the agent doesn’t have that context. It will assume a different one, and you’re left with the code and the assumptions.

How do you check that an AI refactor didn’t break anything?

With tests you wrote before the refactor. Directing the genie to reshape code is fine as long as something independent tells you when it breaks.


I write about this stuff every two weeks. What generated code actually costs, and what to do about it before the bill lands.

Subscribe here.

The post The New Technical Debt first appeared on TestinGil.
Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

Week in Review: Most popular stories on GeekWire for the week of Aug. 2, 2026

1 Share

Get caught up on the latest technology and startup news from the past week. Here are the most popular stories on GeekWire for the week of Aug. 2, 2026.

Sign up to receive these updates every Sunday in your inbox by subscribing to our GeekWire Weekly email newsletter.

Most popular stories on GeekWire

Read the whole story
alvinashcraft
8 hours ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories