Suite Utils

Changelog

What changed in each su-ns-harness release, from the first public 0.4.0 to 0.5.1, covering checker, cache, results and write-safety changes.

On this page

Versions follow semver. The full history, with every fix, is in CHANGELOG.md in the repository.

0.5.1 (2026-09-28)

Reports

  • Report periods as columns in one call. Pass range: "month" or range: "quarter" and the report comes back with one column per period plus a total. NetSuite only accepts the lowercase values and silently ignores others such as MONTH or period. The guard now lowercases MONTH to month, drops range on reports that don't support it, and the skill tells Claude to make one call instead of one per month. The note for a range that had no effect lists the accepted values.
  • Aging reports showed the Current bucket in every column. The bucket columns share a path, and values were looked up by path, so every bucket repeated the Current amount. Values are now read by column id, then by position.
  • Readable column names: 2026-01 … Total, 2026-Q1 … Total, and for aging Current, 1-30, 31-60, 61-90, Over 90, Total. The raw column ids stay in the result's metadata. A warning appears when two value columns are identical on every row.
  • Report summaries print sections per column with the short headers, show range, subsidiaryId and book in full on the Query: line, and note sections such as - No Vendor -.

Data directory

  • The CLI uses its own install's data directory, <plugin>-<marketplace>, even before the first NetSuite call creates it. Other installs' directories are ignored and named once per session, not on every command. nsx doctor --preflight says which directory it will use.
  • After /reload-plugins, which doesn't fire SessionStart, the first NetSuite call's hook note carries the session context and the Cache CLI: line.

0.5.0 (2026-09-27)

Setup

  • Nothing to configure on install. The plugin no longer asks for 8 settings. Every setting has a default, and nsx config set key=value changes one for the next call with no restart. Settings saved earlier through /plugin are no longer read, so re-set any you need with nsx config set.
  • No account id needed. The cache is keyed by the connector, and account_id is optional. Set it to share one cache across connectors that reach the same account.
  • Init runs itself when the cache is empty, instead of asking you to type it.
  • After installing or updating, run /reload-plugins or start a new session. A mid-session install can load late or only partly.
  • Claude can run the refresh and doctor skills itself.

SuiteQL checker

  • Row caps use FETCH FIRST. SELECT * FROM (<GROUP BY … ORDER BY …>) WHERE ROWNUM <= N returns partial sums in arbitrary order on NetSuite. The checker used to suggest that pattern. It now denies it (rownum-over-subquery) and suggests ORDER BY … FETCH FIRST N ROWS ONLY, and nsx sql fix-rownum no longer recommends an outer ROWNUM.
  • OFFSET is denied (offset-ignored). NetSuite ignores OFFSET n ROWS … FETCH NEXT m ROWS ONLY and returns the first rows again, so "page 2" was page 1. When the offset is an exact multiple of the page, the fix suggests pageSize: m, pageIndex: n/m.
  • The rules whose results are always wrong can't be overridden: ROWNUM with aggregates or over a sorted subquery, never-matching ROWNUM, OFFSET, non-SELECT statements, a second statement (multi-statement) and HTML-escaped operators (html-entity).
  • [nolint:<rule>] overrides a single rule. Overridden errors are listed in Claude's context. [nolint] no longer skips the first-use metadata step.
  • New denials from Oracle's SuiteQL docs: WITH clauses, unsupported functions, DATE literals, IN lists over 1000 items, mixed join syntax, right outer (+), + on strings and bracketed identifiers.
  • New undefined-alias error for x.col when x isn't in FROM or JOIN.
  • A table missing from the cached record-type list is denied before the call. Plain [nolint] overrides it.
  • New warnings: string date comparisons, status without BUILTIN.CF, paging without ORDER BY, ORDER BY on an aggregate alias with GROUP BY, and sum-across-subsidiaries for SUM(tal.amount) without subsidiary grouping on multi-currency accounts.
  • Unknown standard columns are a warning, not a deny, because the connector's field metadata misses real columns. The warning suggests the closest match.
  • The approval-status rule no longer fires on a mere mention of approvalstatus. On accounts without approval workflows it used to block an init probe.
  • Fewer false positives from left-join-aggregate, status-without-cf and single-currency detection.

Paging and queries

  • pageIndex: 0 is added along with pageSize. Without it the connector returns every page at once.
  • pageSize below 5 is raised to 5, the connector's minimum.
  • ns_getRecord fields with spaces used to return only the first field. The guard now strips the spaces.
  • Saved Search and System Note searches get the type value the connector needs to run them.

Results

  • Report results keep every line, flattened into line, depth, is_detail, kind and one column per report column.
  • results agg and pivot refuse sums over nested report rows, base amounts across several subsidiaries, a header amount repeated on every line, and id and rate columns.
  • Totals across currencies show "currency unknown" or n/a instead of a sum. Subsidiary reports say which currency their amounts are in.
  • Values like $1,234.00 used to be skipped. They're now parsed, and anything unparsable is counted.
  • results diff compares every --cols column, handles currencies the same way as agg and pivot, and no longer double-counts report rows. A self-diff reports no differences.
  • --where compares dates as dates, takes quoted column names, and combines repeated flags with AND. != and = never match blanks.
  • results head, filter and export take --sort col [--asc].
  • results agg --min/--max works on date columns. --sum and --avg on a date or text column is an error instead of 0.
  • New nsx results concat joins pages of one query.

Cache and account profile

  • Catalog results that would empty or halve a cached section, or can't be parsed, don't replace it.
  • Catalog payloads never reach Claude's context.
  • Init fetches field metadata for the five GL core tables only. Other tables are fetched on first use.
  • Trailing twelve months of revenue is per subsidiary, and the consolidated figure comes from the Income Statement (nsx profile ttm-report, using the last 12 complete accounting periods).
  • When the role can't see subsidiary, base currency is taken from transactions at exchange rate 1 and marked for you to confirm.
  • The CLI no longer switches to an empty sibling data dir. It prefers a dir with a cache, then the one whose hooks fired most recently, and names its choice on stderr.

Errors

  • Rate-limit retries wait 5, 10 and 20 seconds plus jitter (was 2, 4 and 8). The counter resets after 5 quiet minutes.
  • SSS_USAGE_LIMIT_EXCEEDED is treated as a governance error, not a rate limit.
  • A bad column fails with a generic "unexpected SuiteScript error". When the checker flagged a column on that call, the error is reported as bad_field_likely and names it.
  • "Permission Violation" and "Unable to determine record type" inside a successful saved-search result are classified as errors.
  • Error bodies (HTTP 4xx and 5xx strings, {status, title} objects) are recognised as errors.
  • bad_record_type on ns_getRecord gives REST record-type advice, not SuiteQL advice.

Security

  • Path traversal fixed. A crafted recordType could write cache files outside the plugin's data dir. Section names are now validated, and every cache path is checked to stay inside the account dir.
  • Write preview compares exact paths. The diff used to match fields by leaf name, which could hide a change from the approval prompt. The pre hook also rebuilds the diff from the input being sent.
  • Write detection is an allowlist. Every NetSuite tool not known to only read goes through read_only, preview and approval. Unknown tools say so in the prompt.
  • "Write succeeded" needs proof: a record id or success: true. Without either, the result says the outcome is unknown and Claude reads the record back.
  • read_only fails closed on unrecognised values. Turning writes on is refused from anything Claude Code runs, including under script.

CLI

  • --version works. Unknown commands, subcommands, flags and arguments are errors (exit 2) with a suggestion, such as --were → --where.
  • sql lint and preview read stdin only with an explicit -. They used to hang.
  • profile set validates keys and values, and an empty value removes an override.
  • Every hint prints the full CLI command.

0.4.0 (2026-09-27)

First public release.

  • Local metadata cache per account and environment: reports, saved searches, field metadata, subsidiaries, periods and an account profile, with per-section TTLs (ttl_overrides).
  • Large results saved to local files. Claude gets a short summary and a result id, and works on the file with nsx results agg|filter|pivot|diff|export.
  • SuiteQL checker in front of every query: ROWNUM next to GROUP BY or ORDER BY, SELECT *, type and recordtype swaps, unused approvalstatus filters, unknown columns.
  • Connector errors classified with a recovery step. Rate-limit retries run one call at a time.
  • Writes blocked by default (read_only). When enabled, each create or update needs a preview and your approval, and every attempt is in the audit log (nsx audit export).
  • CSV exports neutralize formula-leading cells and warn when the file isn't git-ignored.

Install su-ns-harness

Run both inside Claude Code, then /reload-plugins. Version 0.5.1, free and open source.

  1. /plugin marketplace add suiteutils/suiteutils-netsuite-claude-harness
  2. /plugin install su-ns-harness@suiteutils

Page last updated 2026-09-28. NetSuite is a trademark of Oracle. Claude is a trademark of Anthropic. Suite Utils is not affiliated with either.