Skip to content

fix: Jira error deserialization, Connection header, and exit codes#8

Merged
kolatts merged 2 commits intomainfrom
fix/error-handling-and-exit-codes
Apr 5, 2026
Merged

fix: Jira error deserialization, Connection header, and exit codes#8
kolatts merged 2 commits intomainfrom
fix/error-handling-and-exit-codes

Conversation

@kolatts
Copy link
Copy Markdown
Owner

@kolatts kolatts commented Apr 5, 2026

Summary

  • Jira 400 error details lost: The parser was checking parsed.errors?.[0]?.message (array indexing) but Jira returns errors as a Record<string, string> and errorMessages as string[] — users were only ever seeing "HTTP 400 Bad Request". Now both fields are read and joined into a readable message.
  • Connection: close header: Added to both jiraHeaders() and bitbucketHeaders() in src/lib/http.ts.
  • Standard exit codes: New src/lib/exitCodes.ts with sysexits-style constants (GENERAL_ERROR=1, NETWORK_ERROR=69, AUTH_ERROR=77, CONFIG_ERROR=78). fail() in output.ts now maps HTTP 401/403 → 77 and network failures (status 0) → 69. All hardcoded process.exit(0)/process.exit(1) literals replaced with named constants.

Test plan

  • npm run typecheck passes
  • npm run lint passes
  • npm run build passes
  • Jira 400 response (e.g. missing required field) shows field-level error detail instead of generic "HTTP 400 Bad Request"
  • Auth failure (401/403) exits with code 77: pncli jira issue get TEST-1; echo $?
  • Network failure exits with code 69

🤖 Generated with Claude Code

- Parse Jira 400 responses correctly: read errorMessages (string[]) and
  errors (Record<string,string>) instead of broken array indexing
- Add Connection: close header to Jira and Bitbucket requests
- Introduce src/lib/exitCodes.ts with sysexits-style codes (69, 77, 78)
- fail() now maps HTTP 401/403 → 77, network failures → 69, general → 1
- Replace all hardcoded exit code literals with named constants

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves pncli’s HTTP error reporting (notably for Jira 400 responses), standardizes process exit codes across the CLI, and tweaks default HTTP headers for Jira/Bitbucket requests.

Changes:

  • Improve Jira error deserialization by extracting messages from errorMessages and field-level errors.
  • Add Connection: close to Jira/Bitbucket request headers.
  • Introduce ExitCode constants + HTTP-status→exit-code mapping, and replace hardcoded process.exit(0|1) calls.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/services/config/commands.ts Replaces literal exit codes with ExitCode constants for abort/cancel paths.
src/lib/output.ts Routes fail() exits through exitCodeFromStatus() for auth/network failures.
src/lib/http.ts Enhances non-OK response parsing (Jira formats) and adds Connection: close headers; updates dry-run exit code.
src/lib/exitCodes.ts Adds shared exit code constants and status→exit mapping helper.
src/cli.ts Replaces fatal error exit literal with ExitCode.GENERAL_ERROR.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib/http.ts Outdated
Comment on lines +82 to +86
// Jira: errors is Record<string, string>
if (parsed.errors && typeof parsed.errors === 'object' && !Array.isArray(parsed.errors)) {
for (const [field, msg] of Object.entries(parsed.errors as Record<string, string>)) {
parts.push(`${field}: ${msg}`);
}
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new error-message extraction handles Jira’s errors as an object, but it no longer handles the previously-supported shape where errors is an array of objects with a message field (common in several APIs, and this file previously checked parsed.errors?.[0]?.message). This can regress error detail for those responses. Consider adding a branch for Array.isArray(parsed.errors) that pulls message (and/or joins multiple messages) before falling back to parsed.message.

Copilot uses AI. Check for mistakes.
Comment thread src/lib/exitCodes.ts Outdated

export type ExitCode = (typeof ExitCode)[keyof typeof ExitCode];

export function exitCodeFromStatus(httpStatus: number): number {
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exitCodeFromStatus is currently typed to return number, which loses the stronger ExitCode union and makes it easier to accidentally return an out-of-range value later. Consider changing the return type to ExitCode (and optionally constraining the parameter to number) so callers like fail() stay type-safe.

Suggested change
export function exitCodeFromStatus(httpStatus: number): number {
export function exitCodeFromStatus(httpStatus: number): ExitCode {

Copilot uses AI. Check for mistakes.
- Handle errors-as-array (other APIs) alongside errors-as-object (Jira)
- Tighten exitCodeFromStatus return type to ExitCode union

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kolatts kolatts merged commit 00e78ad into main Apr 5, 2026
1 check passed
@github-actions github-actions Bot mentioned this pull request Apr 5, 2026
kolatts added a commit that referenced this pull request Apr 5, 2026
* fix: Jira error deserialization, Connection header, and exit codes

- Parse Jira 400 responses correctly: read errorMessages (string[]) and
  errors (Record<string,string>) instead of broken array indexing
- Add Connection: close header to Jira and Bitbucket requests
- Introduce src/lib/exitCodes.ts with sysexits-style codes (69, 77, 78)
- fail() now maps HTTP 401/403 → 77, network failures → 69, general → 1
- Replace all hardcoded exit code literals with named constants

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: address PR feedback — array errors shape and ExitCode return type

- Handle errors-as-array (other APIs) alongside errors-as-object (Jira)
- Tighten exitCodeFromStatus return type to ExitCode union

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Sunny Kolattukudy <sunny@imagile.dev>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
kolatts added a commit that referenced this pull request Apr 5, 2026
* feat: add deps command group with CVE scanning and dependency auditing

Implements pncli deps with frisk as the primary command and scan, diff,
outdated, license-check, and connectivity as auxiliary commands. Replaces
the artifactory stub.

- deps frisk: scans all deps for CVEs via OSV.dev querybatch, returns
  structured remediation paths in JSON for agent consumption (Tier 3)
- deps scan: local-only dependency inventory across npm, NuGet, Maven
- deps diff: dep changes between two git refs using git show
- deps outdated: latest versions via Artifactory REST (Tier 2)
- deps license-check: license data per package via Artifactory (Tier 2)
- deps connectivity: diagnoses which tier is available

Parsers handle package-lock.json (v2/v3), yarn.lock, pnpm-lock.yaml,
.csproj/packages.lock.json/Directory.Packages.props/packages.config,
pom.xml, build.gradle, and gradle.lockfile.

Artifactory config uses flat npmRepo/nugetRepo/mavenRepo fields. Each
ecosystem repo is independently optional — missing repos are skipped
silently. config init updated with opt-in Artifactory wizard section.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: update copilot-instructions for deps command group

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: auto-stage copilot-instructions.md in pre-commit hook

The build step regenerates copilot-instructions.md — stage it
automatically so it's never left out of a commit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: address PR #7 review comments

Security:
- Replace execSync string interpolation with execFileSync + arg arrays in
  readFileAtRef and scanRepoAtRef to prevent shell injection from
  user-controlled --from/--to refs

Parser fixes:
- yarn.lock: fix blockRegex to match scoped packages (@scope/name);
  classify direct vs transitive using package.json dep lists and respect
  opts.includeTransitive
- pnpm-lock.yaml: same direct/transitive classification fix; pass
  packageJsonContent through for cross-referencing
- nuget: remove dead propsFiles map that was populated but never read
- maven: resolveProperty now returns null when a ${placeholder} remains
  unresolved, so callers correctly fall through to dependencyManagement
  versions instead of emitting invalid version strings

Diff fixes:
- Key on ecosystem:name:source instead of ecosystem:name to preserve
  multiple versions of the same transitive dep (common in npm)
- summary.unchanged now computed directly from the key set rather than
  mixing collapsed/uncollapsed counts

Directory.Packages.props: walk up parent directories to repo root
instead of only checking the manifest's own directory (props files are
typically at repo root in central package management)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(main): release 1.1.0 (#5)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix: Jira error deserialization, Connection header, and exit codes (#8)

* fix: Jira error deserialization, Connection header, and exit codes

- Parse Jira 400 responses correctly: read errorMessages (string[]) and
  errors (Record<string,string>) instead of broken array indexing
- Add Connection: close header to Jira and Bitbucket requests
- Introduce src/lib/exitCodes.ts with sysexits-style codes (69, 77, 78)
- fail() now maps HTTP 401/403 → 77, network failures → 69, general → 1
- Replace all hardcoded exit code literals with named constants

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: address PR feedback — array errors shape and ExitCode return type

- Handle errors-as-array (other APIs) alongside errors-as-object (Jira)
- Tighten exitCodeFromStatus return type to ExitCode union

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Sunny Kolattukudy <sunny@imagile.dev>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: address second round of PR #7 review comments

- Add shared semver utility (semver.ts) with pre-release/build-metadata
  aware comparison, replacing lossy strip-non-numeric logic in diff and
  artifactory client
- Rename remediation snake_case fields to camelCase (fixAvailable,
  fixedVersions) to match rest of CLI output shape
- Remove always-true availableInArtifactory field; add uncheckedEcosystems
  to OutdatedData so callers know which ecosystems had no repo configured
- Drop unused repoRoot parameter from parseManifests
- Implement NuGet manifest deduplication: when multiple project files share
  the same packages.lock.json, keep only one representative to avoid
  inflated package counts
- Add Kotlin DSL / Groovy parenthesised form support to parseBuildGradle
  (handles both implementation("g:a:v") and implementation 'g:a:v')
- Fix Artifactory config init prompt — deps frisk uses OSV.dev, not
  Artifactory; prompt now only mentions outdated and license-check

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: use write callback before process.exit to avoid libuv assertion on Windows

process.exit() called synchronously after process.stdout/stderr.write() triggers
a libuv assertion on Windows because the write handle is torn down before the
kernel flushes the buffer. Move process.exit() into the write callback in
output.ts fail() and http.ts dry-run paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Sunny Kolattukudy <sunny@imagile.dev>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants