Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ $ --list
# Machine-readable list output
$ --list --output-format json

# Upload the stored log for one execution
$ --upload-log 29d6c026-b168-44a6-8a3f-c3919c7e5327

# Ask a detached isolated execution to stop gracefully
$ --stop 29d6c026-b168-44a6-8a3f-c3919c7e5327

Expand All @@ -166,6 +169,10 @@ include best-effort `processIds` for tracked wrapper processes and detached
screen, tmux, and Docker isolation containers when those native tools can
report them.

`--upload-log` accepts either an execution UUID or an isolation session name. It
looks up the stored `logPath`, installs `gh-upload-log` with Bun or npm if the
uploader is missing, and then streams the uploader output directly.

`--stop` and `--terminate` accept either the execution UUID or the isolation
session/container name. `--stop` sends the graceful interrupt for the backend
(CTRL+C for screen/tmux, `SIGINT` for Docker). `--terminate` uses the backend's
Expand Down Expand Up @@ -323,7 +330,8 @@ For Docker containers, by default the container filesystem is preserved (appears
The tool works in any environment:

- **No `gh` CLI?** - Logs are still saved locally, auto-reporting is skipped
- **No `gh-upload-log`?** - Issue can still be created with local log reference
- **No `gh-upload-log` during auto-reporting?** - Issue can still be created with local log reference
- **No `gh-upload-log` during manual `--upload-log`?** - The uploader is installed on demand
- **Repository not detected?** - Command runs normally with logging
- **No permission to create issue?** - Skipped with a clear message
- **Isolation environment not installed?** - Clear error message with installation instructions
Expand Down
5 changes: 5 additions & 0 deletions docs/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,17 @@ $ echo "Hello World"
$ bun test
$ git status
$ --list
$ --upload-log <id>
```

Use `--status <id>` to inspect one stored execution, or `--list` to see all
stored executions. Query output defaults to Links Notation and can be changed
with `--output-format json` or `--output-format text`.

Use `--upload-log <id>` to look up a stored execution by UUID or session name
and run `gh-upload-log` for its saved log file. If `gh-upload-log` is missing,
the command attempts to install it first with Bun or npm.

For examples checked against the real JavaScript and Rust command output, see
[EXAMPLES.md](EXAMPLES.md).

Expand Down
38 changes: 38 additions & 0 deletions docs/case-studies/issue-128/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Issue 128 Case Study: `$ --upload-log` Support

## Summary

Issue #128 reports that `$ --upload-log <session-id>` is treated as a shell
command and fails with `/bin/sh: 0: Illegal option --`. The requested behavior is
for `start-command` to resolve the tracked execution's `logPath` and run
`gh-upload-log <logPath>` so the uploader output is visible to the caller.

## Evidence Collected

- Issue data: [issue-data.json](issue-data.json)
- Issue comments: [issue-comments.json](issue-comments.json)
- Uploaded log excerpt: [data/gist-evidence.txt](data/gist-evidence.txt)
- Related package metadata: [data/gh-upload-log-npm.json](data/gh-upload-log-npm.json)
- Related repository metadata: [data/gh-upload-log-repo.json](data/gh-upload-log-repo.json)
- Recent merged PRs reviewed for style: [data/recent-merged-prs.json](data/recent-merged-prs.json)

## Implemented Plan

1. Add `--upload-log <uuid-or-session-name>` parsing to the JavaScript and Rust
argument parsers.
2. Keep the option mutually exclusive with `--status`, `--list`, `--stop`,
`--terminate`, and `--cleanup`.
3. Resolve the execution record with the existing `ExecutionStore.get()` lookup,
which already supports UUIDs and isolation session names.
4. Validate that the resolved record has an existing `logPath`.
5. Ensure `gh-upload-log` is available, installing it with Bun or npm when
missing.
6. Run `gh-upload-log <logPath>` with inherited stdio so the upload progress and
resulting URL are visible.
7. Cover the behavior with parser and CLI tests.

## Outcome

The new option is a first-class query action rather than a command passed to the
shell. That removes the reported `/bin/sh` failure mode and reuses the same
execution tracking data that powers `--status`.
19 changes: 19 additions & 0 deletions docs/case-studies/issue-128/data/gh-upload-log-npm.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "gh-upload-log",
"version": "0.8.0",
"description": "A tool to upload log files to GitHub as Gists or repositories",
"repository.url": "git+https://github.com/link-foundation/gh-upload-log.git",
"bin": {
"gh-upload-log": "src/cli.js"
},
"dist-tags": {
"latest": "0.8.0"
},
"dependencies": {
"command-stream": "^0.7.1",
"lino-arguments": "^0.3.0",
"log-lazy": "^1.0.4",
"use-m": "^8.13.7",
"yargs": "^17.7.2"
}
}
1 change: 1 addition & 0 deletions docs/case-studies/issue-128/data/gh-upload-log-repo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"createdAt":"2025-11-13T08:55:12Z","defaultBranchRef":{"name":"main"},"description":"A tool to upload the log to GitHub","isPrivate":false,"latestRelease":{"name":"0.8.0","tagName":"v0.8.0","url":"https://github.com/link-foundation/gh-upload-log/releases/tag/v0.8.0","publishedAt":"2026-04-25T08:36:21Z"},"nameWithOwner":"link-foundation/gh-upload-log","updatedAt":"2026-04-25T08:36:19Z","url":"https://github.com/link-foundation/gh-upload-log","visibility":"PUBLIC"}
27 changes: 27 additions & 0 deletions docs/case-studies/issue-128/data/gist-evidence.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
18392- • Known tokens: 0
18393- • Secretlint: 0 detections
18394- • Custom patterns: 0 detections
18395- • Hex tokens: 3
18396- 🔧 Escaping code blocks in log content for safe embedding...
18397- ⚠️ Log comment too long (2019386 chars), GitHub limit is 65536 chars
18398: 📎 Uploading log using gh-upload-log...
18399-public
18400- 🔍 Repository visibility: public, log upload will be public
18401-github.com
18402- ✓ Logged in to github.com account konard (/home/box/.config/gh/hosts.yml)
18403- - Active account: true
18404- - Git operations protocol: https
--
18406- - Token scopes: 'gist', 'read:org', 'repo', 'user', 'workflow'
18407- 🔒 Sanitized 3 secrets using dual approach:
18408- • Known tokens: 0
18409- • Secretlint: 0 detections
18410- • Custom patterns: 0 detections
18411- • Hex tokens: 3
18412: 📤 Running: gh-upload-log "/tmp/solution-draft-log-pr-1779295404334.txt" --public --description "Solution draft log for https://github.com/ideav/crm/pull/2747" --verbose
18413-Options: {
18414- filePath: "/tmp/solution-draft-log-pr-1779295404334.txt",
18415- isPublic: true,
18416- auto: true,
18417- onlyGist: undefined,
18418- onlyRepository: undefined,
1 change: 1 addition & 0 deletions docs/case-studies/issue-128/data/recent-merged-prs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"headRefName":"issue-126-c5c8579b3ab4","mergedAt":"2026-05-12T21:59:37Z","number":127,"title":"Fix Links Notation process ID formatting","url":"https://github.com/link-foundation/start/pull/127"},{"headRefName":"issue-124-0288905fdd33","mergedAt":"2026-05-10T18:51:46Z","number":125,"title":"Document tested Docker isolation examples","url":"https://github.com/link-foundation/start/pull/125"},{"headRefName":"issue-122-9e4e6b1efac6","mergedAt":"2026-05-03T19:04:45Z","number":123,"title":"Fix CI/CD release publishing and Docker cleanup timeout","url":"https://github.com/link-foundation/start/pull/123"},{"headRefName":"issue-120-22538e16da80","mergedAt":"2026-05-03T17:56:58Z","number":121,"title":"fix(ci): make release preflight accept the Actions installation token","url":"https://github.com/link-foundation/start/pull/121"},{"headRefName":"issue-118-4ed21210786f","mergedAt":"2026-05-03T15:18:55Z","number":119,"title":"Fix all CI/CD issues","url":"https://github.com/link-foundation/start/pull/119"},{"headRefName":"issue-116-01c61894edbd","mergedAt":"2026-05-03T11:07:26Z","number":117,"title":"fix: repair js release pipeline for monorepo layout","url":"https://github.com/link-foundation/start/pull/117"},{"headRefName":"issue-114-8181f01863b9","mergedAt":"2026-05-02T22:47:00Z","number":115,"title":"fix: repair CI/CD release automation","url":"https://github.com/link-foundation/start/pull/115"},{"headRefName":"issue-112-46af7527c0d9","mergedAt":"2026-05-02T07:24:46Z","number":113,"title":"feat: add detached execution controls","url":"https://github.com/link-foundation/start/pull/113"}]
1 change: 1 addition & 0 deletions docs/case-studies/issue-128/issue-comments.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
1 change: 1 addition & 0 deletions docs/case-studies/issue-128/issue-data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"author":{"id":"MDQ6VXNlcjE0MzE5MDQ=","is_bot":false,"login":"konard","name":"Konstantin Diachenko"},"body":"```\nbox@2df581054dbb:~$ $ --status 41e2617a-0741-41f2-a56e-c9e9cbbe8068\nb10d0dfb-9bb4-45a0-873e-23b98878457b\n uuid b10d0dfb-9bb4-45a0-873e-23b98878457b\n pid 1699843\n processIds\n wrapperPid 1699843\n status executed\n exitCode 0\n command \"solve https://github.com/ideav/crm/issues/2746 --model opus --tool claude --attach-logs --verbose --no-tool-check --disable-report-issue --language en\"\n logPath /tmp/start-command/logs/isolation/screen/b10d0dfb-9bb4-45a0-873e-23b98878457b.log\n startTime \"2026-05-20T16:34:52.810Z\"\n endTime \"2026-05-20T18:27:15.636Z\"\n workingDirectory /home/box\n shell /bin/sh\n platform linux\n options\n isolated screen\n isolationMode detached\n sessionName 41e2617a-0741-41f2-a56e-c9e9cbbe8068\n user false\n keepAlive false\n useCommandStream false\nbox@2df581054dbb:~$ gh-upload-log /tmp/start-command/logs/isolation/screen/b10d0dfb-9bb4-45a0-873e-23b98878457b.log\n⏳ Uploading 1.45 MB (🔒 private)...\n- Creating gist b10d0dfb-9bb4-45a0-873e-23b98878457b.log\n✓ Created secret gist b10d0dfb-9bb4-45a0-873e-23b98878457b.log\nhttps://gist.github.com/konard/c5a7ef71b33d38631592b7ddd223ff23\n✅ Gist created (🔒 private)\n🔗 https://gist.github.com/konard/c5a7ef71b33d38631592b7ddd223ff23\n📄 https://gist.githubusercontent.com/konard/c5a7ef71b33d38631592b7ddd223ff23/raw/6e2f607f3914bde708f5749840c433ad48b28398/b10d0dfb-9bb4-45a0-873e-23b98878457b.log\nbox@2df581054dbb:~$ $ --upload-log 41e2617a-0741-41f2-a56e-c9e9cbbe8068\n│ session 433ed252-2145-41c4-afaf-1cfe59838987\n│ start 2026-05-20 18:28:19.150\n│\n$ --upload-log 41e2617a-0741-41f2-a56e-c9e9cbbe8068\n\n/bin/sh: 0: Illegal option --\n\n✗\n│ finish 2026-05-20 18:28:19.206\n│ duration 0.056s\n│ exit 2\n│\n│ log /tmp/start-command/logs/direct/433ed252-2145-41c4-afaf-1cfe59838987.log\n│ session 433ed252-2145-41c4-afaf-1cfe59838987\n\nRepository not detected - automatic issue creation skipped\nbox@2df581054dbb:~$ \n```\n\nThe end result of `$ --upload-log` would be getting the log path from status (internally) and execute it as `$ gh-upload-log /tmp/start-command/logs/isolation/screen/b10d0dfb-9bb4-45a0-873e-23b98878457b.log`.\n\nSo the output of gh-upload-log is visible.\n\nThat option should automatically install gh-upload-log if not installed.\n\nWe need to collect data related about the issue to this repository, make sure we compile that data to `./docs/case-studies/issue-{id}` folder, and use it to do deep case study analysis (also make sure to search online for additional facts and data), list of each and all requirements from the issue, and propose possible solutions and solution plans for each requirement (we should also check known existing components/libraries, that solve similar problem or can help in solutions).\n\nPlease plan and execute everything in a single pull request, you have unlimited time and context, as context auto-compacts and you can continue indefinitely, until it is each and every requirement fully addressed, and everything is totally done.","comments":[],"createdAt":"2026-05-20T18:30:32Z","number":128,"state":"OPEN","title":"Add `$ --upload-log ` support","updatedAt":"2026-05-20T18:30:32Z","url":"https://github.com/link-foundation/start/issues/128"}
36 changes: 36 additions & 0 deletions docs/case-studies/issue-128/online-research.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Online And Repository Research

## gh-upload-log Package

- Repository: <https://github.com/link-foundation/gh-upload-log>
- npm package: <https://www.npmjs.com/package/gh-upload-log>
- Collected metadata shows latest version `0.8.0`, published as the `latest`
npm dist-tag, with a `gh-upload-log` binary entry.
- The repository describes the tool as a log uploader for GitHub. That matches
the issue's requested integration point.

## GitHub Gist Behavior

- GitHub CLI documentation for `gh gist create` says the command creates secret
gists by default and uses `--public` for public gists:
<https://cli.github.com/manual/gh_gist_create>
- GitHub's gist documentation distinguishes public and secret gists and notes
that secret gists are reachable by URL:
<https://docs.github.com/articles/creating-gists>

## Relevant Existing Components

- `ExecutionStore.get(identifier)` already supports UUID lookup and fallback to
`options.sessionName`.
- `--status` already exposes `logPath`, proving the data is stored in execution
records.
- Existing failure reporting already knows about `gh-upload-log`, but it only
used the uploader opportunistically when installed. The new manual command
adds on-demand installation because that is explicitly required for
`--upload-log`.

## Related PR Style

Recent merged PRs show this repository keeps issue-specific case studies,
targeted regression tests, and release fragments in the same PR. See
[data/recent-merged-prs.json](data/recent-merged-prs.json).
24 changes: 24 additions & 0 deletions docs/case-studies/issue-128/requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Requirements

## Functional Requirements

1. `$ --upload-log <id>` must be recognized by `start-command` instead of being
forwarded to the shell.
2. `<id>` must identify a tracked execution by UUID or by isolation session name,
matching the lookup behavior of `--status`.
3. The command must read the execution record's stored `logPath` internally.
4. The command must run `gh-upload-log <logPath>`.
5. `gh-upload-log` stdout and stderr must remain visible to the caller.
6. If `gh-upload-log` is missing, the command must attempt automatic
installation.
7. Missing execution records, missing log paths, and missing log files must
produce clear errors.
8. Query/control modes must remain mutually exclusive.

## Repository Process Requirements

1. Collect issue data under `docs/case-studies/issue-128`.
2. Include deep case-study analysis, requirements, possible solutions, and
relevant online/repository research.
3. Add tests that reproduce the reported bug and verify the fix.
4. Add release metadata for both JavaScript and Rust packages.
24 changes: 24 additions & 0 deletions docs/case-studies/issue-128/root-cause.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Root Cause

`--upload-log` was not part of the wrapper option model in either implementation.
The argument parser therefore treated it as an unknown option. In the no-separator
syntax, unknown options are interpreted as the beginning of the command to run.

That made this invocation:

```bash
$ --upload-log 41e2617a-0741-41f2-a56e-c9e9cbbe8068
```

behave like a shell command roughly equivalent to:

```bash
/bin/sh -c "--upload-log 41e2617a-0741-41f2-a56e-c9e9cbbe8068"
```

The shell then rejected the leading `--upload-log` token as an invalid shell
option, producing the reported `Illegal option --` failure.

The existing execution tracking store already persisted the required `logPath`,
and `--status` already exposed that path. The missing piece was a dedicated query
action that resolves the record and invokes the uploader directly.
63 changes: 63 additions & 0 deletions docs/case-studies/issue-128/solutions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Solution Options

## Option 1: Dedicated Wrapper Option

Add `--upload-log <id>` as a first-class wrapper query mode, parallel to
`--status` and `--list`.

Pros:

- Reuses `ExecutionStore.get()` for UUID and session-name lookup.
- Avoids parsing status text output.
- Prevents the shell fallback that caused the bug.
- Keeps upload progress visible by inheriting process stdio.

Cons:

- Requires JS and Rust parser/CLI updates.

Decision: selected.

## Option 2: Compose Through `--status --output-format json`

Teach a shell script or alias to run `--status`, parse JSON, and call
`gh-upload-log`.

Pros:

- Minimal core changes.

Cons:

- Leaves `$ --upload-log` unsupported.
- Requires fragile external composition and JSON parsing.
- Does not satisfy automatic installation as part of the command.

Decision: rejected.

## Option 3: Extend Automatic Failure Reporting Only

Install `gh-upload-log` during existing failure auto-reporting.

Pros:

- Improves one related path.

Cons:

- Does not provide the requested manual upload command.
- Changes automatic failure reporting side effects.

Decision: rejected for this issue.

## Implementation Plan

1. Extend `WrapperOptions` with `uploadLog`/`upload_log`.
2. Parse `--upload-log <id>` and `--upload-log=<id>`.
3. Include `--upload-log` in mutual-exclusion validation.
4. Add a JS uploader helper for path validation, on-demand installation, and
inherited-stdio execution.
5. Add equivalent Rust CLI handling and failure-handler utilities.
6. Update usage docs and release fragments.
7. Add tests for parsing, mutual exclusion, upload execution, auto-install, and
missing log-file errors.
12 changes: 12 additions & 0 deletions docs/case-studies/issue-128/timeline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Timeline

- 2026-05-20 18:30 UTC: Issue #128 opened with a failing `$ --upload-log`
example and a private uploaded log.
- 2026-05-20: Issue details, comments, PR discussion endpoints, gist evidence,
related repository metadata, npm metadata, and recent merged PRs were
collected.
- 2026-05-20: Reproducing tests were added for parser support and CLI
`--upload-log` behavior.
- 2026-05-20: JavaScript and Rust implementations were updated with first-class
`--upload-log` handling.
- 2026-05-20: Targeted JS and Rust tests passed locally.
5 changes: 5 additions & 0 deletions js/.changeset/issue-128-upload-log.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'start-command': minor
---

Add `--upload-log <id>` to upload a stored execution log with `gh-upload-log`, installing the uploader on demand when it is missing.
17 changes: 17 additions & 0 deletions js/src/bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const { handleFailure } = require('../lib/failure-handler');
const { ExecutionStore, ExecutionRecord } = require('../lib/execution-store');
const { queryStatus, listExecutions } = require('../lib/status-formatter');
const { ControlAction, controlExecution } = require('../lib/execution-control');
const { uploadExecutionLog } = require('../lib/log-uploader');
const { printVersion } = require('../lib/version');
const { createStartBlock, createFinishBlock } = require('../lib/output-blocks');
const { runWithBunSpawn, runWithNodeSpawn } = require('../lib/spawn-helpers');
Expand Down Expand Up @@ -201,6 +202,12 @@ if (wrapperOptions.list) {
process.exit(0);
}

// Handle --upload-log flag
if (wrapperOptions.uploadLog) {
const exitCode = handleUploadLogQuery(wrapperOptions.uploadLog);
process.exit(exitCode);
}

// Handle --stop flag
if (wrapperOptions.stop !== null && wrapperOptions.stop !== undefined) {
handleControlQuery(wrapperOptions.stop, ControlAction.STOP);
Expand Down Expand Up @@ -300,6 +307,16 @@ function handleListQuery(outputFormat) {
}
}

function handleUploadLogQuery(identifier) {
const result = uploadExecutionLog(getExecutionStore(), identifier);
if (result.success) {
return result.exitCode || 0;
}

console.error(`Error: ${result.error}`);
return result.exitCode || 1;
}

function handleControlQuery(identifier, action) {
const result = controlExecution(getExecutionStore(), identifier, action);
if (result.success) {
Expand Down
Loading
Loading