How Much to Write Before Claude Builds It
N.O.R.A is a self-hosted homelab monitoring platform built with Claude Code as the primary executor. The workflow is settled: design sessions produce specifications, specifications become GitHub issues via PowerShell scripts, Claude Code builds against them. The question this post is about is not whether to use this approach. It is the decisions within the workflow that determine whether it produces a codebase you can maintain, or one you can only run.
There are four decisions that matter. Each one has a failure mode in both directions. Getting them wrong in one direction produces vague output and embedded decisions with no record. Getting them wrong in the other direction produces overhead that slows everything down without proportional benefit. The right answer to each shifts depending on whether you are a solo homelab developer or working in a team environment, and this post tries to be honest about both.
When to Write the Full Issue vs. When to Just Ask
Every time a new piece of work starts, the first decision is how much to write before Claude Code runs. A complete issue with schema, interface definitions, dependency notes, and orientation context takes 20 to 40 minutes for a non-trivial feature. Asking Claude Code directly takes 2 minutes. Both paths produce working code. The question is what else they produce.
The full issue produces a permanent record of design intent, predictable output because nothing is left open to AI inference, and a prompt that can be re-run or handed to a different session. The direct ask produces faster code, decisions embedded in the implementation with no separate record, and potential drift from codebase conventions if the AI was not oriented before it started.
The threshold I use: anything that touches the data model, adds a new abstraction, or will be a dependency for something else gets a full issue. Anything that is a bounded fix or a cosmetic change goes direct. A bug that requires a schema migration gets an issue. A bug that requires changing a CSS class does not. The test is whether the decision being made would be hard to reconstruct from the code alone six months from now. If yes, write the issue. If no, the direct path is fine.
In a homelab context, the tolerance for embedded decisions is higher because you are the person who made them. You will remember them, at least for a while. The threshold can sit lower. In a team context the calculation changes, because the spec doubles as communication to other developers, not just to the AI. An issue without a design rationale forces a future reader to reverse-engineer intent from the code. That cost scales with team size and codebase age in ways that are easy to underestimate at the start of a project.
How Much to Specify: Design Decisions vs. Implementation Details
Over-specification has real costs. If you specify every method signature, every variable name, and every error message, you are writing the code twice. The AI handles implementation details well from codebase context. What it does not handle reliably from context is design decisions, because those are the things the codebase does not yet contain.
The boundary: specify anything that would require a design decision if left open. Leave anything that is a natural consequence of the design unspecified. In practice this means: write down the schema, the interface method signatures, the dependency order, the migration number. Do not specify the loop that iterates the rows, the local variable names, or the exact wording of an error message. The AI will resolve those from codebase conventions without prompting. Specifying them is work it does not need.
The failure mode in the other direction is under-specification that looks complete. A spec that lists method names but not signatures appears thorough but leaves the hardest decisions open. "Add a Upsert method" tells the AI nothing about how conflicts are resolved, what the primary key constraint is, or what the return type should be. The spec needs to be complete on the decisions, not just on the topics. A list of topics is an agenda. A list of decisions with their constraints is a spec.
One pattern that surfaces this gap: if you find yourself writing a spec section and then immediately thinking "but that depends on..." — the thing it depends on belongs in the spec, not in your head. Write the dependency down. If you cannot write it down, the design is not finished.
What the Orientation Block Needs to Contain
Every N.O.R.A issue starts with a block that tells Claude Code what to read before it starts building: the claude.md conventions file, any relevant architecture documents, and the current migration number. This block is cheap to write and has a measurable effect on output alignment. Without it, Claude Code makes reasonable guesses about conventions and file locations. With it, those guesses are replaced by instructions.
The decision is what belongs in this block before it becomes noise. Too sparse and the AI misses something it needed. Too verbose and it starts to look like boilerplate that gets treated as background. The rule that has worked: include anything the AI could not reliably infer from the files it is most likely to open first. The current migration number belongs in the block because the AI cannot know it without reading a directory listing. A naming convention for repository interfaces belongs in the block if it is non-standard; it does not belong if it matches a pattern the AI already knows from the codebase.
The claude.md file carries the project-wide conventions. The orientation block in each issue points at it and adds anything issue-specific on top. Keeping these two artifacts separate matters. The claude.md should not become a per-issue checklist, and the issue orientation block should not duplicate what is already in claude.md. If the orientation block starts growing beyond three or four pointers, that is usually a sign that the conventions belong in claude.md and the block should just reference it.
In a homelab context this is mostly a quality-of-output problem. Missing an orientation pointer means a correction round, not a production incident. In a team context the orientation block also functions as onboarding documentation for contributors who may not have the full codebase context before picking up an issue. That secondary function is worth keeping in mind when writing it.
How Fine to Slice the Work
The N.O.R.A API poller work was decomposed into four sequential issues: AP-01 (digest registry table), AP-02 (app profile schema), AP-03 (poller engine), AP-04 (app detail page enrichment). Each was a self-contained unit with defined inputs and outputs. That granularity was deliberate, and it created overhead in both directions. More scripts to write. More sessions to run. More PRs to review. But also: each issue was independently verifiable, each PR was independently reviewable, and when AP-03 needed a correction, AP-01 and AP-02 were already clean and merged.
The trade is between session scope and delivery risk. A single large issue gives Claude Code more context in one session and avoids handoff friction between issues. A sequence of smaller issues is easier to verify incrementally and easier to roll back if something goes wrong.
The failure mode of large issues: they accumulate scope during the design session and become hard to verify completely. If something is wrong in section four of a six-section issue, the PR review surfaces it, but everything else in the issue is already built against the assumption that section four was correct. Correcting it means touching more code than the problem actually required.
The failure mode of small issues: overhead that does not pay for itself. Not every feature has a natural dependency boundary that warrants a separate issue. Splitting for the sake of it produces PS1 scripts and PRs that represent one logical change fragmented across three records. That is harder to read in retrospect, not easier.
The rule that works: decompose at the dependency boundary. Issues that have a natural order because one depends on the table or interface another creates should be separate. Issues that are logically parallel and would typically be done in the same session can be combined. The test is whether each issue can be verified independently. If you cannot answer "what does done look like for this issue" without referencing another issue, it is probably too granular.
What to Carry Into Your Own Environment
These four decisions are not specific to N.O.R.A or to Claude Code. They appear any time an AI executor enters a development workflow, because they are all versions of the same underlying question: how much of the thinking needs to be made explicit before the work starts, and for whom?
The answers shift based on operating context in predictable ways. Solo developers can carry more in their heads and absorb more embedded decisions without losing track. Team environments need more explicit record because more people need to be able to read the code's history and understand it without the original author present. Neither context changes the underlying pressure. Both change how urgently you feel it.
What the N.O.R.A issue history makes visible is what the pressure looks like at scale. More than 260 issues in. Every meaningful decision has a written record. The codebase is readable by anyone who reads the issues. That outcome was not designed in advance. It is a consequence of writing each issue completely enough to be executable. The discipline and the documentation are the same thing.